diff --git a/README.md b/README.md index adbfbbf..6166c95 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,152 @@ require-tree ============ -![NPM badge](https://nodei.co/npm/require-tree.png?compact=true) +![NPM](https://img.shields.io/npm/v/require-tree.svg?style=flat) -Require multiple files at once, creating an object tree that mirrors the directory structure. +A `require()`-like method for directories, returning an object that mirrors the file tree. npm install require-tree -Usage ------ +## Usage - var require_tree = require('require-tree') +Considering this file structure: - // File structure: - // ./models - // user.js - // page.js - // item.js +* models + * user.js + * page.js + * item.js - require_tree('./models') +Requiring the `models` directory will return an object containing each exported module: - // { - // user: [object Object], - // page: [object Object], - // item: [object Object] - // } +```javascript +var require_tree = require('require-tree') +require_tree('./models') +/* { + user: [object Object], + page: [object Object], + item: [object Object] +} */ +``` -With nested directories: +Directories can be deeply nested, and`index.js` files are merged into their parent by default: - // ./api - // /pages - // index.js - // edit.js - // user.js +```javascript +// api/user.js: +module.exports = { + profile: function(){}, + posts: function(){} +} - // api/pages/index.js: - module.exports = { - list: function(){ ... } - } +// api/pages/index.js: +module.exports = { + list: function(){} +} - // api/pages/edit.js: - module.exports = { - getPermissions: function(){ ... }, - remove: function(){ ... } - } +// api/pages/edit.js: +module.exports = { + getPermissions: function(){}, + remove: function(){} +} - // api/user.js: - module.exports = { - profile: function(){ ... }, - posts: function(){ ... } - } +var api = require_tree('./api') +``` - var api = require_tree('./api') +This will yield - // api.pages.list - // api.pages.edit.getPermissions - // api.pages.edit.remove - // api.user.profile - // api.user.posts +- `api.user.profile` +- `api.user.posts` +- `api.pages.list` +- `api.pages.edit.getPermissions` +- `api.pages.edit.remove` -### Limitations +### Options -Since v0.3 `require-tree` will resolve paths relative to the requiring module, like `require` itself. +```javascript +require_tree(path, { options }) +``` -Since it depends on `module.parent` being set correctly, either `require-tree` must be explicitly required within the current module scope, or you need to provide an absolute path like `__dirname + '/somepath'`. +#### { name: string | function (exports) } +Use a property of the exports object as it's key (instead of the filename) in the final object. -### Options +```javascript +// models/user-model.js +module.exports = { + id: 'user', + attrs: {} +} + +require_tree('./models', { name: 'id' }) +require_tree('./models', { name: function (obj) { return obj.id } }) +// => { user: { id: 'user', attrs: {} } } +``` + +#### { filter: string | regexp | function } + +Filter the required files. Strings can use a wildcard '*' and are expanded into regular expressions. You can also provide your own RegExp, or a function that receives the filename as an argument, and returns `true` or `false`. + +```javascript +require_tree('./path', { filter: '*-model' }) +require_tree('./path', { filter: /^model/ }) +require_tree('./path', { filter: function (filename) { return filename.indexOf('model') === 0 } }) +``` + +#### { keys: string | array | regexp | function } + +Use to return only certain keys from exported objects. + +```javascript +require_tree('./models', { keys: 'at*' }) +require_tree('./models', { keys: ['attrs'] }) +require_tree('./models', { keys: function (key){ return key.indexOf('attrs') >= 0 } }) +// => { user: { attrs: {} } } +``` + +#### { each: function } + +Callback to run after each file is required. Doesn't modify the exported object. + +```javascript +require_tree('./items', { each: function (obj) { items.insert(obj) } }) +``` - { - name : [String | Function(exports, file)] // the object's property to use as key - main : [String | Array | Function(exports, file)] // what keys should be exported - index : [Boolean] // load 'index.js' files (`true` by default) - filter : [String | RegExp | Function] // filter pattern - each : [Function] // callback to run after each module - } +#### { transform: function } + +Same as `each`, but can modify the exports object. + +```javascript +require_tree('./models', { transform: function (obj) { return new Model(obj) } }) +``` + +#### index + + * `merge` (default): merges the `index.js` exports at the root of it's parent + * `ignore`: causes `index.js` files to *not be loaded* at all + * `preserve`: puts the `index.js` export object under the `.index` property + +For backwards compatibility, a value of `true` is equal to `preserve`, while `false` is equal to `ignore`. + +- controllers + - index.js + - users.js + - ... + +```javascript +// controllers/index.js: +module.exports = { + init: function () { ... } +} + +var controllers = require_tree('./controllers', { index: 'preserve' }) +controllers.index.init() + +var controllers = require_tree('./controllers', { index: 'ignore' }) +controllers.index // undefined + +var controllers = require_tree('./controllers', { index: 'merge' }) +controllers.init() +``` + +### Limitations +`require-tree` must always be required in the local scope, and never shared between modules. Paths are resolved relative to the parent module, like `require` itself, so it's behaviour depends on `module.parent` being set correctly. If necessary, you can use absolute paths (`__dirname + '/path'`) or set the `NODE_PATH` environment variable. diff --git a/index.js b/index.js index 52a246e..25a3a5b 100644 --- a/index.js +++ b/index.js @@ -3,101 +3,104 @@ /* require() a directory tree -(c) Ricardo Tomasi 2012 +(c) Ricardo Tomasi 2012-2015 License: MIT */ -var fs = require('fs') - , path = require('path') +var fs = require('fs') + , path = require('path') + , extend = require('extend') + , type = require('component-type') -function type (obj) { - return Object.prototype.toString.call(obj) - .match(/object\s(\w+)/, '')[1] - .toLowerCase() -} - -function isDirectory (fpath) { - return fs.lstatSync(fpath).isDirectory() -} - -function extend (obj, source) { - for (var key in source){ - if (source.hasOwnProperty(key)) { - obj[key] = source[key] - } +function getFilter (filter) { + switch (type(filter)) { + case 'array' : filter = filter.join('|') + case 'string' : filter = new RegExp('^('+filter.replace('*', '.*')+')$', 'i') + case 'regexp' : filter = filter.exec.bind(filter) + case 'function' : return filter } - return obj + // return undefined } function require_tree (directory, options) { options = extend({ - index: true + index: 'merge' }, options) var baseDir = process.env.NODE_PATH || path.dirname(module.parent.filename) , dir = path.resolve(baseDir, directory) , forbidden = ['.json', '.node'] - , filter = options.filter + , filter = getFilter(options.filter) || Boolean , tree = {} + , files = fs.readdirSync(dir) - var files = fs.readdirSync(dir) - - switch (type(filter)) { - case 'string' : filter = new RegExp(filter) - case 'regexp' : filter = filter.exec.bind(filter) - case 'function': files = files.filter(filter) - } - - files.forEach(function(file){ + files.filter(filter).forEach(function(file){ var ext = path.extname(file) , name = path.basename(file, ext) , fpath = path.join(dir, file) - , item, obj + , exported, _exported - if (isDirectory(fpath)) { + if (fs.lstatSync(fpath).isDirectory()) { tree[file] = require_tree(fpath, options) return } - if (forbidden.indexOf(ext) >= 0 - || !(ext in require.extensions) - ) return + if (forbidden.indexOf(ext) >= 0 || + !(ext in require.extensions) || + (name === 'index' && /ignore|false/.test(options.index))) { + return + } + + exported = _exported = require(fpath) - obj = item = require(fpath) + if (options.keys) { + var keys = getFilter(options.keys) + exported = {} + Object.getOwnPropertyNames(_exported).forEach(function(key){ + if (keys(key)) { + exported[key] = _exported[key] + } + }) + } switch (type(options.main)) { case 'string': - obj = item[options.main] + exported = exported[options.main] break case 'function': - obj = options.main(item, file) + exported = options.main(exported, file) break case 'array': - obj = {} - Object.keys(item).forEach(function(key){ - if (options.main.indexOf(key) >= 0) - obj[key] = item[key] + exported = {} + Object.getOwnPropertyNames(_exported).forEach(function(key){ + if (options.main.indexOf(key) >= 0) { + exported[key] = _exported[key] + } }) } switch (type(options.name)) { case 'string': - name = item[options.name] + name = exported[options.name] break case 'function': - name = options.name(item, file) + name = options.name(exported, file) + } + + if (type(options.transform) === 'function') { + exported = options.transform(exported, file, path.join(dir, file)) } - if (options.index && name === 'index') { - extend(tree, obj) - } else { - tree[name] = obj + if (name === 'index' && options.index === 'merge') { + extend(tree, exported) + } else { /* if name !== 'index' || options.index === 'preserve' */ + tree[name] = exported } - options.each && options.each(obj, file, path.join(dir, file)) + options.each && options.each(exported, file, path.join(dir, file)) }) @@ -107,4 +110,4 @@ function require_tree (directory, options) { module.exports = require_tree // Necessary to get the current `module.parent` and resolve paths correctly. -delete require.cache[__filename]; +delete require.cache[__filename] diff --git a/package.json b/package.json index b728de3..9c7facd 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,12 @@ { "name": "require-tree", "description": "require() whole directory trees", - "version": "0.3.3", + "version": "1.0.0", "main": "index.js", - "dependencies": {}, + "dependencies": { + "component-type": "^1.1.0", + "extend": "^2.0.0" + }, "devDependencies": { "mocha": "" }, diff --git a/test/app.spec.js b/test/app.spec.js index afe2368..77a780b 100644 --- a/test/app.spec.js +++ b/test/app.spec.js @@ -68,12 +68,47 @@ describe('paths', function(){ }) -describe('when given the index:false option', function(){ +describe('when given the index option', function(){ + + describe('preserve', function () { + it('should put index under it\'s own key', function(){ + var poa = require_tree('./porto-alegre', { index: 'preserve' }) + assert.deepEqual(poa, { + bairros: bairros, + index: { city: POA.city } + }) + }) + it('should put index under it\'s own key (index: true)', function(){ + var poa = require_tree('./porto-alegre', { index: true }) + assert.deepEqual(poa, { + bairros: bairros, + index: { city: POA.city } + }) + }) + }) + + describe('merge', function () { + it('should merge index at the root', function(){ + var poa = require_tree('./porto-alegre', { index: 'merge' }) + assert.deepEqual(poa, { + bairros: bairros, + city: POA.city + }) + }) + }) - it('should ignore index files', function(){ - var poa = require_tree('./porto-alegre', { index: false }) - assert.deepEqual(poa, { - bairros: bairros + describe('ignore', function () { + it('should not load index', function(){ + var poa = require_tree('./porto-alegre', { index: 'ignore' }) + assert.deepEqual(poa, { + bairros: bairros + }) + }) + it('should not load index (index: false)', function(){ + var poa = require_tree('./porto-alegre', { index: false }) + assert.deepEqual(poa, { + bairros: bairros + }) }) }) @@ -93,6 +128,18 @@ describe('when given the each option', function(){ }) +describe('when given the transform option', function (){ + it('should redefine the exports object', function(){ + var bairros = require_tree('./porto-alegre/bairros', { + transform: function(){ return null } + }) + assert.deepEqual(bairros, { + 'bomfim': null + , 'santo-antonio': null + }) + }) +}) + describe('when given the name option', function(){ describe('with a function', function(){ @@ -142,51 +189,30 @@ describe('when given the name option', function(){ }) -describe('when given the main option', function(){ +describe('when given the keys option', function(){ describe('with a function', function(){ - it('should run once for each file', function(){ + it('should run once for each key', function(){ var x = 0 function increment(){ x++ } - require_tree('./porto-alegre/bairros', { - main: increment - }) - assert.equal(x, 2) - }) - - it('should define the export object', function(){ - var bairros = require_tree('./porto-alegre/bairros', { - main: function(){ - return { nil: null } - } - }) - assert.deepEqual(bairros, { - 'bomfim': { nil: null } - , 'santo-antonio': { nil: null } - }) + require_tree('./porto-alegre/bairros', { keys: increment }) + assert.equal(x, 6) }) - it('should be able to access the export object', function(){ + it ('should filter the exports object', function(){ var bairros = require_tree('./porto-alegre/bairros', { - main: function(obj){ - return obj.rating - } + keys: ['rating', 'zone'] }) - assert.deepEqual(bairros, { - 'bomfim': '*****' - , 'santo-antonio': '***' - }) - }) }) describe('with an array', function(){ - it ('should filter the exports object', function(){ + it('should filter the exports object', function(){ var bairros = require_tree('./porto-alegre/bairros', { - main: ['rating', 'zone'] + keys: ['rating', 'zone'] }) assert.deepEqual(bairros, { 'bomfim': { @@ -224,7 +250,7 @@ describe('when given the filter option', function(){ it('should filter the required files', function(){ var res = require_tree('./porto-alegre/bairros', { - filter: 'bom' + filter: 'bom*' }) assert.deepEqual(res.bomfim, bairros.bomfim) assert.deepEqual(Object.keys(res), ['bomfim']) diff --git a/test/testindex/index/a.js b/test/testindex/index/a.js new file mode 100644 index 0000000..6d414a1 --- /dev/null +++ b/test/testindex/index/a.js @@ -0,0 +1 @@ +module.exports = 'A'; \ No newline at end of file