123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- var ProtoList = require('proto-list')
- , path = require('path')
- , fs = require('fs')
- , ini = require('ini')
- , EE = require('events').EventEmitter
- , url = require('url')
- , http = require('http')
- var exports = module.exports = function () {
- var args = [].slice.call(arguments)
- , conf = new ConfigChain()
- while(args.length) {
- var a = args.shift()
- if(a) conf.push
- ( 'string' === typeof a
- ? json(a)
- : a )
- }
- return conf
- }
- //recursively find a file...
- var find = exports.find = function () {
- var rel = path.join.apply(null, [].slice.call(arguments))
- function find(start, rel) {
- var file = path.join(start, rel)
- try {
- fs.statSync(file)
- return file
- } catch (err) {
- if(path.dirname(start) !== start) // root
- return find(path.dirname(start), rel)
- }
- }
- return find(__dirname, rel)
- }
- var parse = exports.parse = function (content, file, type) {
- content = '' + content
- // if we don't know what it is, try json and fall back to ini
- // if we know what it is, then it must be that.
- if (!type) {
- try { return JSON.parse(content) }
- catch (er) { return ini.parse(content) }
- } else if (type === 'json') {
- if (this.emit) {
- try { return JSON.parse(content) }
- catch (er) { this.emit('error', er) }
- } else {
- return JSON.parse(content)
- }
- } else {
- return ini.parse(content)
- }
- }
- var json = exports.json = function () {
- var args = [].slice.call(arguments).filter(function (arg) { return arg != null })
- var file = path.join.apply(null, args)
- var content
- try {
- content = fs.readFileSync(file,'utf-8')
- } catch (err) {
- return
- }
- return parse(content, file, 'json')
- }
- var env = exports.env = function (prefix, env) {
- env = env || process.env
- var obj = {}
- var l = prefix.length
- for(var k in env) {
- if(k.indexOf(prefix) === 0)
- obj[k.substring(l)] = env[k]
- }
- return obj
- }
- exports.ConfigChain = ConfigChain
- function ConfigChain () {
- EE.apply(this)
- ProtoList.apply(this, arguments)
- this._awaiting = 0
- this._saving = 0
- this.sources = {}
- }
- // multi-inheritance-ish
- var extras = {
- constructor: { value: ConfigChain }
- }
- Object.keys(EE.prototype).forEach(function (k) {
- extras[k] = Object.getOwnPropertyDescriptor(EE.prototype, k)
- })
- ConfigChain.prototype = Object.create(ProtoList.prototype, extras)
- ConfigChain.prototype.del = function (key, where) {
- // if not specified where, then delete from the whole chain, scorched
- // earth style
- if (where) {
- var target = this.sources[where]
- target = target && target.data
- if (!target) {
- return this.emit('error', new Error('not found '+where))
- }
- delete target[key]
- } else {
- for (var i = 0, l = this.list.length; i < l; i ++) {
- delete this.list[i][key]
- }
- }
- return this
- }
- ConfigChain.prototype.set = function (key, value, where) {
- var target
- if (where) {
- target = this.sources[where]
- target = target && target.data
- if (!target) {
- return this.emit('error', new Error('not found '+where))
- }
- } else {
- target = this.list[0]
- if (!target) {
- return this.emit('error', new Error('cannot set, no confs!'))
- }
- }
- target[key] = value
- return this
- }
- ConfigChain.prototype.get = function (key, where) {
- if (where) {
- where = this.sources[where]
- if (where) where = where.data
- if (where && Object.hasOwnProperty.call(where, key)) return where[key]
- return undefined
- }
- return this.list[0][key]
- }
- ConfigChain.prototype.save = function (where, type, cb) {
- if (typeof type === 'function') cb = type, type = null
- var target = this.sources[where]
- if (!target || !(target.path || target.source) || !target.data) {
- // TODO: maybe save() to a url target could be a PUT or something?
- // would be easy to swap out with a reddis type thing, too
- return this.emit('error', new Error('bad save target: '+where))
- }
- if (target.source) {
- var pref = target.prefix || ''
- Object.keys(target.data).forEach(function (k) {
- target.source[pref + k] = target.data[k]
- })
- return this
- }
- var type = type || target.type
- var data = target.data
- if (target.type === 'json') {
- data = JSON.stringify(data)
- } else {
- data = ini.stringify(data)
- }
- this._saving ++
- fs.writeFile(target.path, data, 'utf8', function (er) {
- this._saving --
- if (er) {
- if (cb) return cb(er)
- else return this.emit('error', er)
- }
- if (this._saving === 0) {
- if (cb) cb()
- this.emit('save')
- }
- }.bind(this))
- return this
- }
- ConfigChain.prototype.addFile = function (file, type, name) {
- name = name || file
- var marker = {__source__:name}
- this.sources[name] = { path: file, type: type }
- this.push(marker)
- this._await()
- fs.readFile(file, 'utf8', function (er, data) {
- if (er) this.emit('error', er)
- this.addString(data, file, type, marker)
- }.bind(this))
- return this
- }
- ConfigChain.prototype.addEnv = function (prefix, env, name) {
- name = name || 'env'
- var data = exports.env(prefix, env)
- this.sources[name] = { data: data, source: env, prefix: prefix }
- return this.add(data, name)
- }
- ConfigChain.prototype.addUrl = function (req, type, name) {
- this._await()
- var href = url.format(req)
- name = name || href
- var marker = {__source__:name}
- this.sources[name] = { href: href, type: type }
- this.push(marker)
- http.request(req, function (res) {
- var c = []
- var ct = res.headers['content-type']
- if (!type) {
- type = ct.indexOf('json') !== -1 ? 'json'
- : ct.indexOf('ini') !== -1 ? 'ini'
- : href.match(/\.json$/) ? 'json'
- : href.match(/\.ini$/) ? 'ini'
- : null
- marker.type = type
- }
- res.on('data', c.push.bind(c))
- .on('end', function () {
- this.addString(Buffer.concat(c), href, type, marker)
- }.bind(this))
- .on('error', this.emit.bind(this, 'error'))
- }.bind(this))
- .on('error', this.emit.bind(this, 'error'))
- .end()
- return this
- }
- ConfigChain.prototype.addString = function (data, file, type, marker) {
- data = this.parse(data, file, type)
- this.add(data, marker)
- return this
- }
- ConfigChain.prototype.add = function (data, marker) {
- if (marker && typeof marker === 'object') {
- var i = this.list.indexOf(marker)
- if (i === -1) {
- return this.emit('error', new Error('bad marker'))
- }
- this.splice(i, 1, data)
- marker = marker.__source__
- this.sources[marker] = this.sources[marker] || {}
- this.sources[marker].data = data
- // we were waiting for this. maybe emit 'load'
- this._resolve()
- } else {
- if (typeof marker === 'string') {
- this.sources[marker] = this.sources[marker] || {}
- this.sources[marker].data = data
- }
- // trigger the load event if nothing was already going to do so.
- this._await()
- this.push(data)
- process.nextTick(this._resolve.bind(this))
- }
- return this
- }
- ConfigChain.prototype.parse = exports.parse
- ConfigChain.prototype._await = function () {
- this._awaiting++
- }
- ConfigChain.prototype._resolve = function () {
- this._awaiting--
- if (this._awaiting === 0) this.emit('load', this)
- }
|