aboutsummaryrefslogtreecommitdiff
path: root/node_modules/config-chain/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/config-chain/index.js')
-rw-r--r--node_modules/config-chain/index.js282
1 files changed, 282 insertions, 0 deletions
diff --git a/node_modules/config-chain/index.js b/node_modules/config-chain/index.js
new file mode 100644
index 0000000..0ef3a91
--- /dev/null
+++ b/node_modules/config-chain/index.js
@@ -0,0 +1,282 @@
+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)
+}