|
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 |
|
} |
|
|
|
|
|
|
|
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) |
|
return find(path.dirname(start), rel) |
|
} |
|
} |
|
return find(__dirname, rel) |
|
} |
|
|
|
var parse = exports.parse = function (content, file, type) { |
|
content = '' + content |
|
|
|
|
|
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 = {} |
|
} |
|
|
|
|
|
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 (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) { |
|
|
|
|
|
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 |
|
|
|
this._resolve() |
|
} else { |
|
if (typeof marker === 'string') { |
|
this.sources[marker] = this.sources[marker] || {} |
|
this.sources[marker].data = data |
|
} |
|
|
|
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) |
|
} |
|
|