You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					283 lines
				
				7.0 KiB
			
		
		
			
		
	
	
					283 lines
				
				7.0 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								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)
							 | 
						||
| 
								 | 
							
								}
							 |