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.
		
		
		
		
		
			
		
			
				
					
					
						
							282 lines
						
					
					
						
							7.0 KiB
						
					
					
				
			
		
		
	
	
							282 lines
						
					
					
						
							7.0 KiB
						
					
					
				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) | 
						|
}
 | 
						|
 |