├── .gitignore ├── .travis.yml ├── LICENCE ├── index.js ├── package.json ├── readme.markdown └── test ├── broken.js ├── broken.json ├── chain-class.js ├── env.js ├── find-file.js ├── get.js ├── ignore-unfound-file.js ├── ini.js └── save.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | npm_debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | - ppc64le 4 | language: node_js 5 | node_js: 6 | - '10' 7 | - '8' 8 | - '6' 9 | - '4' 10 | - '.12' 11 | - '.10' 12 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Dominic Tarr 2 | 3 | Permission is hereby granted, free of charge, 4 | to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom 10 | the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 20 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var ProtoList = require('proto-list') 2 | , path = require('path') 3 | , fs = require('fs') 4 | , ini = require('ini') 5 | , EE = require('events').EventEmitter 6 | , url = require('url') 7 | , http = require('http') 8 | 9 | var exports = module.exports = function () { 10 | var args = [].slice.call(arguments) 11 | , conf = new ConfigChain() 12 | 13 | while(args.length) { 14 | var a = args.shift() 15 | if(a) conf.push 16 | ( 'string' === typeof a 17 | ? json(a) 18 | : a ) 19 | } 20 | 21 | return conf 22 | } 23 | 24 | //recursively find a file... 25 | 26 | var find = exports.find = function () { 27 | var rel = path.join.apply(null, [].slice.call(arguments)) 28 | 29 | function find(start, rel) { 30 | var file = path.join(start, rel) 31 | try { 32 | fs.statSync(file) 33 | return file 34 | } catch (err) { 35 | if(path.dirname(start) !== start) // root 36 | return find(path.dirname(start), rel) 37 | } 38 | } 39 | return find(__dirname, rel) 40 | } 41 | 42 | var parse = exports.parse = function (content, file, type) { 43 | content = '' + content 44 | // if we don't know what it is, try json and fall back to ini 45 | // if we know what it is, then it must be that. 46 | if (!type) { 47 | try { return JSON.parse(content) } 48 | catch (er) { return ini.parse(content) } 49 | } else if (type === 'json') { 50 | if (this.emit) { 51 | try { return JSON.parse(content) } 52 | catch (er) { this.emit('error', er) } 53 | } else { 54 | return JSON.parse(content) 55 | } 56 | } else { 57 | return ini.parse(content) 58 | } 59 | } 60 | 61 | var json = exports.json = function () { 62 | var args = [].slice.call(arguments).filter(function (arg) { return arg != null }) 63 | var file = path.join.apply(null, args) 64 | var content 65 | try { 66 | content = fs.readFileSync(file,'utf-8') 67 | } catch (err) { 68 | return 69 | } 70 | return parse(content, file, 'json') 71 | } 72 | 73 | var env = exports.env = function (prefix, env) { 74 | env = env || process.env 75 | var obj = {} 76 | var l = prefix.length 77 | for(var k in env) { 78 | if(k.indexOf(prefix) === 0) 79 | obj[k.substring(l)] = env[k] 80 | } 81 | 82 | return obj 83 | } 84 | 85 | exports.ConfigChain = ConfigChain 86 | function ConfigChain () { 87 | EE.apply(this) 88 | ProtoList.apply(this, arguments) 89 | this._awaiting = 0 90 | this._saving = 0 91 | this.sources = {} 92 | } 93 | 94 | // multi-inheritance-ish 95 | var extras = { 96 | constructor: { value: ConfigChain } 97 | } 98 | Object.keys(EE.prototype).forEach(function (k) { 99 | extras[k] = Object.getOwnPropertyDescriptor(EE.prototype, k) 100 | }) 101 | ConfigChain.prototype = Object.create(ProtoList.prototype, extras) 102 | 103 | ConfigChain.prototype.del = function (key, where) { 104 | // if not specified where, then delete from the whole chain, scorched 105 | // earth style 106 | if (where) { 107 | var target = this.sources[where] 108 | target = target && target.data 109 | if (!target) { 110 | return this.emit('error', new Error('not found '+where)) 111 | } 112 | delete target[key] 113 | } else { 114 | for (var i = 0, l = this.list.length; i < l; i ++) { 115 | delete this.list[i][key] 116 | } 117 | } 118 | return this 119 | } 120 | 121 | ConfigChain.prototype.set = function (key, value, where) { 122 | var target 123 | 124 | if (where) { 125 | target = this.sources[where] 126 | target = target && target.data 127 | if (!target) { 128 | return this.emit('error', new Error('not found '+where)) 129 | } 130 | } else { 131 | target = this.list[0] 132 | if (!target) { 133 | return this.emit('error', new Error('cannot set, no confs!')) 134 | } 135 | } 136 | target[key] = value 137 | return this 138 | } 139 | 140 | ConfigChain.prototype.get = function (key, where) { 141 | if (where) { 142 | where = this.sources[where] 143 | if (where) where = where.data 144 | if (where && Object.hasOwnProperty.call(where, key)) return where[key] 145 | return undefined 146 | } 147 | return this.list[0][key] 148 | } 149 | 150 | ConfigChain.prototype.save = function (where, type, cb) { 151 | if (typeof type === 'function') cb = type, type = null 152 | var target = this.sources[where] 153 | if (!target || !(target.path || target.source) || !target.data) { 154 | // TODO: maybe save() to a url target could be a PUT or something? 155 | // would be easy to swap out with a reddis type thing, too 156 | return this.emit('error', new Error('bad save target: '+where)) 157 | } 158 | 159 | if (target.source) { 160 | var pref = target.prefix || '' 161 | Object.keys(target.data).forEach(function (k) { 162 | target.source[pref + k] = target.data[k] 163 | }) 164 | return this 165 | } 166 | 167 | var type = type || target.type 168 | var data = target.data 169 | if (target.type === 'json') { 170 | data = JSON.stringify(data) 171 | } else { 172 | data = ini.stringify(data) 173 | } 174 | 175 | this._saving ++ 176 | fs.writeFile(target.path, data, 'utf8', function (er) { 177 | this._saving -- 178 | if (er) { 179 | if (cb) return cb(er) 180 | else return this.emit('error', er) 181 | } 182 | if (this._saving === 0) { 183 | if (cb) cb() 184 | this.emit('save') 185 | } 186 | }.bind(this)) 187 | return this 188 | } 189 | 190 | ConfigChain.prototype.addFile = function (file, type, name) { 191 | name = name || file 192 | var marker = {__source__:name} 193 | this.sources[name] = { path: file, type: type } 194 | this.push(marker) 195 | this._await() 196 | fs.readFile(file, 'utf8', function (er, data) { 197 | if (er) this.emit('error', er) 198 | this.addString(data, file, type, marker) 199 | }.bind(this)) 200 | return this 201 | } 202 | 203 | ConfigChain.prototype.addEnv = function (prefix, env, name) { 204 | name = name || 'env' 205 | var data = exports.env(prefix, env) 206 | this.sources[name] = { data: data, source: env, prefix: prefix } 207 | return this.add(data, name) 208 | } 209 | 210 | ConfigChain.prototype.addUrl = function (req, type, name) { 211 | this._await() 212 | var href = url.format(req) 213 | name = name || href 214 | var marker = {__source__:name} 215 | this.sources[name] = { href: href, type: type } 216 | this.push(marker) 217 | http.request(req, function (res) { 218 | var c = [] 219 | var ct = res.headers['content-type'] 220 | if (!type) { 221 | type = ct.indexOf('json') !== -1 ? 'json' 222 | : ct.indexOf('ini') !== -1 ? 'ini' 223 | : href.match(/\.json$/) ? 'json' 224 | : href.match(/\.ini$/) ? 'ini' 225 | : null 226 | marker.type = type 227 | } 228 | 229 | res.on('data', c.push.bind(c)) 230 | .on('end', function () { 231 | this.addString(Buffer.concat(c), href, type, marker) 232 | }.bind(this)) 233 | .on('error', this.emit.bind(this, 'error')) 234 | 235 | }.bind(this)) 236 | .on('error', this.emit.bind(this, 'error')) 237 | .end() 238 | 239 | return this 240 | } 241 | 242 | ConfigChain.prototype.addString = function (data, file, type, marker) { 243 | data = this.parse(data, file, type) 244 | this.add(data, marker) 245 | return this 246 | } 247 | 248 | ConfigChain.prototype.add = function (data, marker) { 249 | if (marker && typeof marker === 'object') { 250 | var i = this.list.indexOf(marker) 251 | if (i === -1) { 252 | return this.emit('error', new Error('bad marker')) 253 | } 254 | this.splice(i, 1, data) 255 | marker = marker.__source__ 256 | this.sources[marker] = this.sources[marker] || {} 257 | this.sources[marker].data = data 258 | // we were waiting for this. maybe emit 'load' 259 | this._resolve() 260 | } else { 261 | if (typeof marker === 'string') { 262 | this.sources[marker] = this.sources[marker] || {} 263 | this.sources[marker].data = data 264 | } 265 | // trigger the load event if nothing was already going to do so. 266 | this._await() 267 | this.push(data) 268 | process.nextTick(this._resolve.bind(this)) 269 | } 270 | return this 271 | } 272 | 273 | ConfigChain.prototype.parse = exports.parse 274 | 275 | ConfigChain.prototype._await = function () { 276 | this._awaiting++ 277 | } 278 | 279 | ConfigChain.prototype._resolve = function () { 280 | this._awaiting-- 281 | if (this._awaiting === 0) this.emit('load', this) 282 | } 283 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config-chain", 3 | "version": "1.1.12", 4 | "licenses": [ 5 | { 6 | "type": "MIT", 7 | "url": "https://raw.githubusercontent.com/dominictarr/config-chain/master/LICENCE" 8 | } 9 | ], 10 | "description": "HANDLE CONFIGURATION ONCE AND FOR ALL", 11 | "homepage": "http://github.com/dominictarr/config-chain", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/dominictarr/config-chain.git" 15 | }, 16 | "files": [ 17 | "index.js" 18 | ], 19 | "dependencies": { 20 | "proto-list": "~1.2.1", 21 | "ini": "^1.3.4" 22 | }, 23 | "devDependencies": { 24 | "tap": "0.3.0" 25 | }, 26 | "author": "Dominic Tarr (http://dominictarr.com)", 27 | "scripts": { 28 | "test": "tap test/*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # config-chain 2 | 3 | A module for loading custom configurations 4 | 5 | ## NOTE: Feature Freeze 6 | 7 | [![locked](http://badges.github.io/stability-badges/dist/locked.svg)](http://github.com/badges/stability-badges) 8 | 9 | This module is frozen. 10 | 11 | In general, we recommend using [rc](https://github.com/dominictarr/rc) instead, 12 | but as [npm](https://github.com/npmjs/npm) depends on this, it cannot be changed. 13 | 14 | 15 | ## Install 16 | 17 | ```sh 18 | yarn add config-chain 19 | 20 | # npm users 21 | npm install --save config-chain 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```js 27 | const cc = require('config-chain'); 28 | 29 | console.log(cc.env('TERM_', process.env)); 30 | /* 31 | { SESSION_ID: 'w1:5F38', 32 | PROGRAM_VERSION: '3.1.2', 33 | PROGRAM: 'iTerm.app' } 34 | */ 35 | ``` 36 | 37 | The `.env` function gets all the keys on the provided object which are 38 | prefixed by the specified prefix, removes the prefix, and puts the values on a new object. 39 | 40 |
41 | 42 | ## Full Usage 43 | 44 | ``` js 45 | 46 | // npm install config-chain 47 | 48 | var cc = require('config-chain') 49 | , opts = require('optimist').argv //ALWAYS USE OPTIMIST FOR COMMAND LINE OPTIONS. 50 | , env = opts.env || process.env.YOUR_APP_ENV || 'dev' //SET YOUR ENV LIKE THIS. 51 | 52 | // EACH ARG TO CONFIGURATOR IS LOADED INTO CONFIGURATION CHAIN 53 | // EARLIER ITEMS OVERIDE LATER ITEMS 54 | // PUTS COMMAND LINE OPTS FIRST, AND DEFAULTS LAST! 55 | 56 | //strings are interpereted as filenames. 57 | //will be loaded synchronously 58 | 59 | var conf = 60 | cc( 61 | //OVERRIDE SETTINGS WITH COMMAND LINE OPTS 62 | opts, 63 | 64 | //ENV VARS IF PREFIXED WITH 'myApp_' 65 | 66 | cc.env('myApp_'), //myApp_foo = 'like this' 67 | 68 | //FILE NAMED BY ENV 69 | path.join(__dirname, 'config.' + env + '.json'), 70 | 71 | //IF `env` is PRODUCTION 72 | env === 'prod' 73 | ? path.join(__dirname, 'special.json') //load a special file 74 | : null //NULL IS IGNORED! 75 | 76 | //SUBDIR FOR ENV CONFIG 77 | path.join(__dirname, 'config', env, 'config.json'), 78 | 79 | //SEARCH PARENT DIRECTORIES FROM CURRENT DIR FOR FILE 80 | cc.find('config.json'), 81 | 82 | //PUT DEFAULTS LAST 83 | { 84 | host: 'localhost' 85 | port: 8000 86 | }) 87 | 88 | var host = conf.get('host') 89 | 90 | // or 91 | 92 | var host = conf.store.host 93 | 94 | ``` 95 | 96 | Finally, flexible configurations! 👌 97 | 98 | ## Custom Configuations 99 | 100 | ```javascript 101 | var cc = require('config-chain') 102 | 103 | // all the stuff you did before 104 | var config = cc({ 105 | some: 'object' 106 | }, 107 | cc.find('config.json'), 108 | cc.env('myApp_') 109 | ) 110 | // CONFIGS AS A SERVICE, aka "CaaS", aka EVERY DEVOPS DREAM OMG! 111 | .addUrl('http://configurator:1234/my-configs') 112 | // ASYNC FTW! 113 | .addFile('/path/to/file.json') 114 | 115 | // OBJECTS ARE OK TOO, they're SYNC but they still ORDER RIGHT 116 | // BECAUSE PROMISES ARE USED BUT NO, NOT *THOSE* PROMISES, JUST 117 | // ACTUAL PROMISES LIKE YOU MAKE TO YOUR MOM, KEPT OUT OF LOVE 118 | .add({ another: 'object' }) 119 | 120 | // DIE A THOUSAND DEATHS IF THIS EVER HAPPENS!! 121 | .on('error', function (er) { 122 | // IF ONLY THERE WAS SOMETHIGN HARDER THAN THROW 123 | // MY SORROW COULD BE ADEQUATELY EXPRESSED. /o\ 124 | throw er 125 | }) 126 | 127 | // THROW A PARTY IN YOUR FACE WHEN ITS ALL LOADED!! 128 | .on('load', function (config) { 129 | console.awesome('HOLY SHIT!') 130 | }) 131 | ``` 132 | 133 | # API Docs 134 | 135 | ## cc(...args) 136 | 137 | MAKE A CHAIN AND ADD ALL THE ARGS. 138 | 139 | If the arg is a STRING, then it shall be a JSON FILENAME. 140 | 141 | RETURN THE CHAIN! 142 | 143 | ## cc.json(...args) 144 | 145 | Join the args into a JSON filename! 146 | 147 | SYNC I/O! 148 | 149 | ## cc.find(relativePath) 150 | 151 | SEEK the RELATIVE PATH by climbing the TREE OF DIRECTORIES. 152 | 153 | RETURN THE FOUND PATH! 154 | 155 | SYNC I/O! 156 | 157 | ## cc.parse(content, file, type) 158 | 159 | Parse the content string, and guess the type from either the 160 | specified type or the filename. 161 | 162 | RETURN THE RESULTING OBJECT! 163 | 164 | NO I/O! 165 | 166 | ## cc.env(prefix, env=process.env) 167 | 168 | Get all the keys on the provided object which are 169 | prefixed by the specified prefix, removes the prefix, and puts the values on a new object. 170 | 171 | RETURN THE RESULTING OBJECT! 172 | 173 | NO I/O! 174 | 175 | ## cc.ConfigChain() 176 | 177 | The ConfigChain class for CRAY CRAY JQUERY STYLE METHOD CHAINING! 178 | 179 | One of these is returned by the main exported function, as well. 180 | 181 | It inherits (prototypically) from 182 | [ProtoList](https://github.com/isaacs/proto-list/), and also inherits 183 | (parasitically) from 184 | [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) 185 | 186 | It has all the methods from both, and except where noted, they are 187 | unchanged. 188 | 189 | ### LET IT BE KNOWN THAT chain IS AN INSTANCE OF ConfigChain. 190 | 191 | ## chain.sources 192 | 193 | A list of all the places where it got stuff. The keys are the names 194 | passed to addFile or addUrl etc, and the value is an object with some 195 | info about the data source. 196 | 197 | ## chain.addFile(filename, type, [name=filename]) 198 | 199 | Filename is the name of the file. Name is an arbitrary string to be 200 | used later if you desire. Type is either 'ini' or 'json', and will 201 | try to guess intelligently if omitted. 202 | 203 | Loaded files can be saved later. 204 | 205 | ## chain.addUrl(url, type, [name=url]) 206 | 207 | Same as the filename thing, but with a url. 208 | 209 | Can't be saved later. 210 | 211 | ## chain.addEnv(prefix, env, [name='env']) 212 | 213 | Add all the keys from the env object that start with the prefix. 214 | 215 | ## chain.addString(data, file, type, [name]) 216 | 217 | Parse the string and add it to the set. (Mainly used internally.) 218 | 219 | ## chain.add(object, [name]) 220 | 221 | Add the object to the set. 222 | 223 | ## chain.root {Object} 224 | 225 | The root from which all the other config objects in the set descend 226 | prototypically. 227 | 228 | Put your defaults here. 229 | 230 | ## chain.set(key, value, name) 231 | 232 | Set the key to the value on the named config object. If name is 233 | unset, then set it on the first config object in the set. (That is, 234 | the one with the highest priority, which was added first.) 235 | 236 | ## chain.get(key, [name]) 237 | 238 | Get the key from the named config object explicitly, or from the 239 | resolved configs if not specified. 240 | 241 | ## chain.save(name, type) 242 | 243 | Write the named config object back to its origin. 244 | 245 | Currently only supported for env and file config types. 246 | 247 | For files, encode the data according to the type. 248 | 249 | ## chain.on('save', function () {}) 250 | 251 | When one or more files are saved, emits `save` event when they're all 252 | saved. 253 | 254 | ## chain.on('load', function (chain) {}) 255 | 256 | When the config chain has loaded all the specified files and urls and 257 | such, the 'load' event fires. 258 | -------------------------------------------------------------------------------- /test/broken.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var cc = require('..') 4 | var assert = require('assert') 5 | 6 | 7 | //throw on invalid json 8 | assert.throws(function () { 9 | cc(__dirname + '/broken.json') 10 | }) 11 | -------------------------------------------------------------------------------- /test/broken.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config-chain", 3 | "version": "0.3.0", 4 | "description": "HANDLE CONFIGURATION ONCE AND FOR ALL", 5 | "homepage": "http://github.com/dominictarr/config-chain", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/dominictarr/config-chain.git" 9 | } 10 | //missing , and then this comment. this json is intentionally invalid 11 | "dependencies": { 12 | "proto-list": "1", 13 | "ini": "~1.0.2" 14 | }, 15 | "bundleDependencies": ["ini"], 16 | "REM": "REMEMBER TO REMOVE BUNDLING WHEN/IF ISAACS MERGES ini#7", 17 | "author": "Dominic Tarr (http://dominictarr.com)", 18 | "scripts": { 19 | "test": "node test/find-file.js && node test/ini.js && node test/env.js" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/chain-class.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test 2 | var CC = require('../index.js').ConfigChain 3 | 4 | var env = { foo_blaz : 'blzaa', foo_env : 'myenv' } 5 | var jsonObj = { blaz: 'json', json: true } 6 | var iniObj = { 'x.y.z': 'xyz', blaz: 'ini' } 7 | 8 | var fs = require('fs') 9 | var ini = require('ini') 10 | 11 | fs.writeFileSync('/tmp/config-chain-class.json', JSON.stringify(jsonObj)) 12 | fs.writeFileSync('/tmp/config-chain-class.ini', ini.stringify(iniObj)) 13 | 14 | var http = require('http') 15 | var reqs = 0 16 | http.createServer(function (q, s) { 17 | if (++reqs === 2) this.close() 18 | if (q.url === '/json') { 19 | // make sure that the requests come back from the server 20 | // out of order. they should still be ordered properly 21 | // in the resulting config object set. 22 | setTimeout(function () { 23 | s.setHeader('content-type', 'application/json') 24 | s.end(JSON.stringify({ 25 | blaz: 'http', 26 | http: true, 27 | json: true 28 | })) 29 | }, 200) 30 | } else { 31 | s.setHeader('content-type', 'application/ini') 32 | s.end(ini.stringify({ 33 | blaz: 'http', 34 | http: true, 35 | ini: true, 36 | json: false 37 | })) 38 | } 39 | }).listen(1337) 40 | 41 | test('basic class test', function (t) { 42 | var cc = new CC() 43 | var expectlist = 44 | [ { blaz: 'json', json: true }, 45 | { 'x.y.z': 'xyz', blaz: 'ini' }, 46 | { blaz: 'blzaa', env: 'myenv' }, 47 | { blaz: 'http', http: true, json: true }, 48 | { blaz: 'http', http: true, ini: true, json: false } ] 49 | 50 | cc.addFile('/tmp/config-chain-class.json') 51 | .addFile('/tmp/config-chain-class.ini') 52 | .addEnv('foo_', env) 53 | .addUrl('http://localhost:1337/json') 54 | .addUrl('http://localhost:1337/ini') 55 | .on('load', function () { 56 | t.same(cc.list, expectlist) 57 | t.same(cc.snapshot, { blaz: 'json', 58 | json: true, 59 | 'x.y.z': 'xyz', 60 | env: 'myenv', 61 | http: true, 62 | ini: true }) 63 | 64 | cc.del('blaz', '/tmp/config-chain-class.json') 65 | t.same(cc.snapshot, { blaz: 'ini', 66 | json: true, 67 | 'x.y.z': 'xyz', 68 | env: 'myenv', 69 | http: true, 70 | ini: true }) 71 | cc.del('blaz') 72 | t.same(cc.snapshot, { json: true, 73 | 'x.y.z': 'xyz', 74 | env: 'myenv', 75 | http: true, 76 | ini: true }) 77 | cc.shift() 78 | t.same(cc.snapshot, { 'x.y.z': 'xyz', 79 | env: 'myenv', 80 | http: true, 81 | json: true, 82 | ini: true }) 83 | cc.shift() 84 | t.same(cc.snapshot, { env: 'myenv', 85 | http: true, 86 | json: true, 87 | ini: true }) 88 | cc.shift() 89 | t.same(cc.snapshot, { http: true, 90 | json: true, 91 | ini: true }) 92 | cc.shift() 93 | t.same(cc.snapshot, { http: true, 94 | ini: true, 95 | json: false }) 96 | cc.shift() 97 | t.same(cc.snapshot, {}) 98 | t.end() 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /test/env.js: -------------------------------------------------------------------------------- 1 | var cc = require('..') 2 | var assert = require('assert') 3 | 4 | assert.deepEqual({ 5 | hello: true 6 | }, cc.env('test_', { 7 | 'test_hello': true, 8 | 'ignore_this': 4, 9 | 'ignore_test_this_too': [] 10 | })) 11 | -------------------------------------------------------------------------------- /test/find-file.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs') 3 | , assert = require('assert') 4 | , objx = { 5 | rand: Math.random() 6 | } 7 | 8 | fs.writeFileSync('/tmp/random-test-config.json', JSON.stringify(objx)) 9 | 10 | var cc = require('../') 11 | var path = cc.find('tmp/random-test-config.json') 12 | 13 | assert.equal(path, '/tmp/random-test-config.json') -------------------------------------------------------------------------------- /test/get.js: -------------------------------------------------------------------------------- 1 | var cc = require("../"); 2 | 3 | var chain = cc() 4 | , name = "forFun"; 5 | 6 | chain 7 | .add({ 8 | __sample:"for fun only" 9 | }, name) 10 | .on("load", function() { 11 | //It throw exception here 12 | console.log(chain.get("__sample", name)); 13 | //But if I drop the name param, it run normally and return as expected: "for fun only" 14 | //console.log(chain.get("__sample")); 15 | }); 16 | -------------------------------------------------------------------------------- /test/ignore-unfound-file.js: -------------------------------------------------------------------------------- 1 | 2 | var cc = require('..') 3 | 4 | //should not throw 5 | cc(__dirname, 'non_existing_file') 6 | -------------------------------------------------------------------------------- /test/ini.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var cc =require('..') 4 | var INI = require('ini') 5 | var assert = require('assert') 6 | 7 | function test(obj) { 8 | 9 | var _json, _ini 10 | var json = cc.parse (_json = JSON.stringify(obj)) 11 | var ini = cc.parse (_ini = INI.stringify(obj)) 12 | console.log(_ini, _json) 13 | assert.deepEqual(json, ini) 14 | } 15 | 16 | 17 | test({hello: true}) 18 | 19 | -------------------------------------------------------------------------------- /test/save.js: -------------------------------------------------------------------------------- 1 | var CC = require('../index.js').ConfigChain 2 | var test = require('tap').test 3 | 4 | var f1 = '/tmp/f1.ini' 5 | var f2 = '/tmp/f2.json' 6 | 7 | var ini = require('ini') 8 | 9 | var f1data = {foo: {bar: 'baz'}, bloo: 'jaus'} 10 | var f2data = {oof: {rab: 'zab'}, oolb: 'suaj'} 11 | 12 | var fs = require('fs') 13 | 14 | fs.writeFileSync(f1, ini.stringify(f1data), 'utf8') 15 | fs.writeFileSync(f2, JSON.stringify(f2data), 'utf8') 16 | 17 | test('test saving and loading ini files', function (t) { 18 | new CC() 19 | .add({grelb:'blerg'}, 'opt') 20 | .addFile(f1, 'ini', 'inifile') 21 | .addFile(f2, 'json', 'jsonfile') 22 | .on('load', function (cc) { 23 | 24 | t.same(cc.snapshot, { grelb: 'blerg', 25 | bloo: 'jaus', 26 | foo: { bar: 'baz' }, 27 | oof: { rab: 'zab' }, 28 | oolb: 'suaj' }) 29 | 30 | t.same(cc.list, [ { grelb: 'blerg' }, 31 | { bloo: 'jaus', foo: { bar: 'baz' } }, 32 | { oof: { rab: 'zab' }, oolb: 'suaj' } ]) 33 | 34 | cc.set('grelb', 'brelg', 'opt') 35 | .set('foo', 'zoo', 'inifile') 36 | .set('oof', 'ooz', 'jsonfile') 37 | .save('inifile') 38 | .save('jsonfile') 39 | .on('save', function () { 40 | t.equal(fs.readFileSync(f1, 'utf8'), 41 | "bloo=jaus\nfoo=zoo\n") 42 | t.equal(fs.readFileSync(f2, 'utf8'), 43 | "{\"oof\":\"ooz\",\"oolb\":\"suaj\"}") 44 | 45 | t.same(cc.snapshot, { grelb: 'brelg', 46 | bloo: 'jaus', 47 | foo: 'zoo', 48 | oof: 'ooz', 49 | oolb: 'suaj' }) 50 | 51 | t.same(cc.list, [ { grelb: 'brelg' }, 52 | { bloo: 'jaus', foo: 'zoo' }, 53 | { oof: 'ooz', oolb: 'suaj' } ]) 54 | 55 | t.pass('ok') 56 | t.end() 57 | }) 58 | }) 59 | }) 60 | --------------------------------------------------------------------------------