diff --git a/lib/nconf.js b/lib/nconf.js index be81056d..3a71a18d 100644 --- a/lib/nconf.js +++ b/lib/nconf.js @@ -6,34 +6,32 @@ */ var fs = require('fs'), - async = require('async'), common = require('./nconf/common'), Provider = require('./nconf/provider').Provider, nconf = module.exports = new Provider(); -// -// Expose the version from the package.json using `pkginfo`. -// -require('pkginfo')(module, 'version'); - // // Setup all stores as lazy-loaded getters. // +nconf.engines = {}; fs.readdirSync(__dirname + '/nconf/stores').forEach(function (file) { - var store = file.replace('.js', ''), - name = common.capitalize(store); - - nconf.__defineGetter__(name, function () { - return require('./nconf/stores/' + store)[name]; + Object.defineProperty(nconf.engines, file.replace('.js', ''), { + get: function () { + return require('./nconf/stores/' + file); + }, + enumerable: true + }); + Object.defineProperty(nconf, file.replace('.js', '').replace(/./, function (c) {return c.toUpperCase()}), { + get: function () { + return require('./nconf/stores/' + file); + }, + enumerable: true }); }); // // Expose the various components included with nconf // -nconf.key = common.key; -nconf.path = common.path; -nconf.loadFiles = common.loadFiles; -nconf.loadFilesSync = common.loadFilesSync; +nconf.common = common; nconf.formats = require('./nconf/formats'); nconf.Provider = Provider; diff --git a/lib/nconf/common.js b/lib/nconf/common.js index 2c092e38..d675592b 100644 --- a/lib/nconf/common.js +++ b/lib/nconf/common.js @@ -7,8 +7,7 @@ var fs = require('fs'), async = require('async'), - formats = require('./formats'), - Memory = require('./stores/memory').Memory; + Memory = require('./stores/memory'); var common = exports; @@ -20,9 +19,39 @@ var common = exports; // '' should still be respected as a path. // common.path = function (key) { - return key == null ? [] : key.split(':'); + return key == null ? [] : ('' + key).split(':'); }; +common.scope = function (path, target) { + // + // Scope into the object to get the appropriate nested context + // + for (var i = 0; i < path.length; i++) { + key = path[i]; + if (typeof target === 'object' && key in target) { + target = target[key]; + continue; + } + return void 0; + } + return target; +} + +common.ensure = function (path, target) { + // + // Scope into the object to get the appropriate nested context + // + for (var i = 0; i < path.length - 1; i++) { + key = path[i]; + if (target && !(key in target)) { + target[key] = {}; + } + target = target[key]; + } + + return target; +} + // // ### function key (arguments) // Returns a `:` joined string from the `arguments`. @@ -39,7 +68,8 @@ common.key = function () { // common.loadFiles = function (files, callback) { if (!files) { - return callback(null, {}); + callback(null, {}); + return; } var options = Array.isArray(files) ? { files: files } : files; @@ -48,7 +78,7 @@ common.loadFiles = function (files, callback) { // Set the default JSON format if not already // specified // - options.format = options.format || formats.json; + options.format = options.format || require('../nconf').formats.json; function parseFile (file, next) { fs.readFile(file, function (err, data) { @@ -70,7 +100,7 @@ common.loadFiles = function (files, callback) { // common.loadFilesSync = function (files) { if (!files) { - return; + return void 0; } // @@ -78,7 +108,7 @@ common.loadFilesSync = function (files) { // specified // var options = Array.isArray(files) ? { files: files } : files; - options.format = options.format || formats.json; + options.format = options.format || require('../nconf').formats.json; return common.merge(files.map(function (file) { return options.format.parse(fs.readFileSync(file, 'utf8')); @@ -102,12 +132,3 @@ common.merge = function (objs) { return store.store; }; - -// -// ### function capitalize (str) -// #### @str {string} String to capitalize -// Capitalizes the specified `str`. -// -common.capitalize = function (str) { - return str && str[0].toUpperCase() + str.slice(1); -}; diff --git a/lib/nconf/formats.js b/lib/nconf/formats.js index f32268c2..fb546430 100644 --- a/lib/nconf/formats.js +++ b/lib/nconf/formats.js @@ -7,13 +7,11 @@ var ini = require('ini'); -var formats = exports; - // // ### @json // Standard JSON format which pretty prints `.stringify()`. // -formats.json = { +exports.json = { stringify: function (obj, replacer, spacing) { return JSON.stringify(obj, replacer || null, spacing || 2) }, @@ -25,4 +23,4 @@ formats.json = { // Standard INI format supplied from the `ini` module // http://en.wikipedia.org/wiki/INI_file // -formats.ini = ini; +exports.ini = ini; diff --git a/lib/nconf/provider.js b/lib/nconf/provider.js index 413df5f2..1f16d8d3 100644 --- a/lib/nconf/provider.js +++ b/lib/nconf/provider.js @@ -7,6 +7,7 @@ var async = require('async'), common = require('./common'); + // // ### function Provider (options) @@ -29,8 +30,10 @@ var Provider = exports.Provider = function (options) { // Define wrapper functions for using basic stores // in this instance // -['argv', 'env'].forEach(function (type) { +['argv', 'env', 'http'].forEach(function (type) { Provider.prototype[type] = function (options) { + options = options || {}; + options.type = type; return this.add(type, options); }; }); @@ -48,14 +51,12 @@ var Provider = exports.Provider = function (options) { // Provider.prototype.file = function (key, options) { if (arguments.length == 1) { - options = typeof key === 'string' ? { file: key } : key; + options = key; key = 'file'; } - else { - options = typeof options === 'string' - ? { file: options } - : options; - } + options = typeof options === 'string' + ? { file: options } + : options; options.type = 'file'; return this.add(key, options); @@ -68,9 +69,7 @@ Provider.prototype.file = function (key, options) { ['defaults', 'overrides'].forEach(function (type) { Provider.prototype[type] = function (options) { options = options || {}; - if (!options.type) { - options.type = 'literal'; - } + options.type = options.type || 'literal'; return this.add(type, options); }; @@ -85,29 +84,20 @@ Provider.prototype.file = function (key, options) { // will be used instead: // // provider.use('file'); -// provider.use('file', { type: 'file', filename: '/path/to/userconf' }) +// provider.use('file', { type: 'file', file: '/path/to/userconf' }) // Provider.prototype.use = function (name, options) { options = options || {}; var type = options.type || name; - function sameOptions (store) { - return Object.keys(options).every(function (key) { - return options[key] === store[key]; - }); - } - var store = this.stores[name], update = store && !sameOptions(store); - if (!store || update) { - if (update) { - this.remove(name); - } - - this.add(name, options); + if (store) { + this.remove(name); } + this.add(name, options); return this; }; @@ -121,18 +111,30 @@ Provider.prototype.use = function (name, options) { // provider.add('memory'); // provider.add('userconf', { type: 'file', filename: '/path/to/userconf' }) // -Provider.prototype.add = function (name, options) { +var nconf = null; +Provider.prototype.add = function (name, options, cb) { options = options || {}; - var type = options.type || name; - - if (!require('../nconf')[common.capitalize(type)]) { - throw new Error('Cannot add store with unknown type: ' + type); + var store; + nconf = nconf || require('../nconf'); + if (options instanceof nconf.engines.memory) { + store = this.stores[name] = options; } - - this.stores[name] = this.create(type, options); - - if (this.stores[name].loadSync) { - this.stores[name].loadSync(); + else { + var type = options.type || name; + if (!nconf.engines[type]) { + throw new Error('Cannot add store with unknown type: ' + type); + } + + store = this.stores[name] = new nconf.engines[type](options); + } + if (cb && store.load) { + store.load(cb); + } + else if (store.loadSync) { + store.loadSync(); + } + else { + throw new Error('Unable to load store with name: ' + name) } return this; @@ -150,17 +152,6 @@ Provider.prototype.remove = function (name) { return this; }; -// -// ### function create (type, options) -// #### @type {string} Type of the nconf store to use. -// #### @options {Object} Options for the store instance. -// Creates a store of the specified `type` using the -// specified `options`. -// -Provider.prototype.create = function (type, options) { - return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options); -}; - // // ### function init (options) // #### @options {Object} Options to initialize this instance with. @@ -186,19 +177,6 @@ Provider.prototype.init = function (options) { self.add(store.name || name || store.type, store); }); } - - // - // Add any read-only sources to this instance - // - if (options.source) { - this.sources.push(this.create(options.source.type || options.source.name, options.source)); - } - else if (options.sources) { - Object.keys(options.sources).forEach(function (name) { - var source = options.sources[name]; - self.sources.push(self.create(source.type || source.name || name, source)); - }); - } }; // @@ -212,60 +190,31 @@ Provider.prototype.get = function (key, callback) { // If there is no callback we can short-circuit into the default // logic for traversing stores. // - if (!callback) { - return this._execute('get', 1, key, callback); - } - - // - // Otherwise the asynchronous, hierarchical `get` is - // slightly more complicated because we do not need to traverse - // the entire set of stores, but up until there is a defined value. - // - var current = 0, - names = Object.keys(this.stores), - self = this, - response, - mergeObjs = []; - - async.whilst(function () { - return typeof response === 'undefined' && current < names.length; - }, function (next) { - var store = self.stores[names[current]]; - current++; - - if (store.get.length >= 2) { - return store.get(key, function (err, value) { - if (err) { - return next(err); - } - - response = value; - - // Merge objects if necessary - if (typeof response === 'object' && !Array.isArray(response)) { - mergeObjs.push(response); - response = undefined; - } - - next(); - }); - } - - response = store.get(key); - - // Merge objects if necessary - if (typeof response === 'object' && !Array.isArray(response)) { - mergeObjs.push(response); - response = undefined; + var self = this; + var results = null; + var mergedValue = void 0; + async.forEach(Object.keys(this.stores).reverse(), function (storeName, next) { + var store = self.stores[storeName]; + var value = store.get(key); + if (value !== void 0) { + if (value && typeof value === 'object' && !Array.isArray(value)) { + ;(results || (results = [])).push(value); + } + else { + results = null; + mergedValue = value; + } } - next(); }, function (err) { - if (!err && mergeObjs.length) { - response = common.merge(mergeObjs.reverse()); + if (err) { + onError(err, callback); + } + if (results !== null) { + mergedValue = common.merge(results.reverse()); } - return err ? callback(err) : callback(null, response); }); + return mergedValue; }; // @@ -276,7 +225,12 @@ Provider.prototype.get = function (key, callback) { // Sets the `value` for the specified `key` in this instance. // Provider.prototype.set = function (key, value, callback) { - return this._execute('set', 2, key, value, callback); + var self = this; + async.forEach(Object.keys(this.stores), function (storeName, next) { + var store = self.stores[storeName]; + store.set(key, value); + next(); + }, callback); }; // @@ -285,7 +239,12 @@ Provider.prototype.set = function (key, value, callback) { // Clears all keys associated with this instance. // Provider.prototype.reset = function (callback) { - return this._execute('reset', 0, callback); + var self = this; + async.forEach(Object.keys(this.stores), function (storeName, next) { + var store = self.stores[storeName]; + store.reset(); + next(); + }, callback); }; // @@ -295,7 +254,12 @@ Provider.prototype.reset = function (callback) { // Removes the value for the specified `key` from this instance. // Provider.prototype.clear = function (key, callback) { - return this._execute('clear', 1, key, callback); + var self = this; + async.forEach(Object.keys(this.stores), function (storeName, next) { + var store = self.stores[storeName]; + store.clear(); + next(); + }, callback); }; // @@ -308,26 +272,13 @@ Provider.prototype.clear = function (key, callback) { // 1. If the existing value `key` is not an Object, it will be completely overwritten. // 2. If `key` is not supplied, then the `value` will be merged into the root. // -Provider.prototype.merge = function () { - var self = this, - args = Array.prototype.slice.call(arguments), - callback = typeof args[args.length - 1] === 'function' && args.pop(), - value = args.pop(), - key = args.pop(); - - function mergeProperty (prop, next) { - return self._execute('merge', 2, prop, value[prop], next); - } - - if (!key) { - if (Array.isArray(value) || typeof value !== 'object') { - return onError(new Error('Cannot merge non-Object into top-level.'), callback); - } - - return async.forEach(Object.keys(value), mergeProperty, callback || function () { }) - } - - return this._execute('merge', 2, key, value, callback); +Provider.prototype.merge = function (key, value, callback) { + var self = this; + async.forEach(Object.keys(this.stores), function (storeName, next) { + var store = self.stores[storeName]; + store.merge(key, value); + next(); + }, callback); }; // @@ -366,7 +317,8 @@ Provider.prototype.load = function (callback) { function loadBatch (targets, done) { if (!done) { - return common.merge(targets.map(loadStoreSync)); + common.merge(targets.map(loadStoreSync)); + return; } async.map(targets, loadStore, function (err, objs) { @@ -398,7 +350,8 @@ Provider.prototype.load = function (callback) { // if (!callback) { mergeSources(loadBatch(sourceHierarchy)); - return loadBatch(getStores()); + loadBatch(getStores()); + return; } loadBatch(sourceHierarchy, function (err, data) { @@ -426,133 +379,21 @@ Provider.prototype.load = function (callback) { // ignored. Returns an object consisting of all of the data which was // actually saved. // -Provider.prototype.save = function (value, callback) { - if (!callback && typeof value === 'function') { - callback = value; - value = null; - } - - var self = this, - names = Object.keys(this.stores); - - function saveStoreSync(memo, name) { - var store = self.stores[name]; - - // - // If the `store` doesn't have a `saveSync` method, - // just ignore it and continue. - // - if (store.saveSync) { - var ret = store.saveSync(); - if (typeof ret == 'object' && ret !== null) { - memo.push(ret); - } - } - return memo; - } - - function saveStore(memo, name, next) { - var store = self.stores[name]; - - // - // If the `store` doesn't have a `save` or saveSync` - // method(s), just ignore it and continue. - // - - if (store.save) { - return store.save(function (err, data) { - if (err) { - return next(err); - } - - if (typeof data == 'object' && data !== null) { - memo.push(data); - } - - next(null, memo); - }); - } - else if (store.saveSync) { - memo.push(store.saveSync()); - } - - next(null, memo); - } - - // - // If we don't have a callback and the current - // store is capable of saving synchronously - // then do so. - // - if (!callback) { - return common.merge(names.reduce(saveStoreSync, [])); - } - - async.reduce(names, [], saveStore, function (err, objs) { - return err ? callback(err) : callback(null, common.merge(objs)); +Provider.prototype.save = function (callback) { + var self = this; + var wasAsync = false; + var result; + async.map(Object.keys(this.stores), function (storeName, next) { + var store = self.stores[storeName]; + store.save ? (wasAsync = true, store.save(next)) : next(null, store.saveSync ? store.saveSync() : void 0); + }, function (err, stores) { + if (err) onError(err, callback); + result = common.merge(stores); + callback && callback(null, result); }); + if (!wasAsync) return result; }; -// -// ### @private function _execute (action, syncLength, [arguments]) -// #### @action {string} Action to execute on `this.store`. -// #### @syncLength {number} Function length of the sync version. -// #### @arguments {Array} Arguments array to apply to the action -// Executes the specified `action` on all stores for this instance, ensuring a callback supplied -// to a synchronous store function is still invoked. -// -Provider.prototype._execute = function (action, syncLength /* [arguments] */) { - var args = Array.prototype.slice.call(arguments, 2), - callback = typeof args[args.length - 1] === 'function' && args.pop(), - destructive = ['set', 'clear', 'merge', 'reset'].indexOf(action) !== -1, - self = this, - response, - mergeObjs = []; - - function runAction (name, next) { - var store = self.stores[name]; - - if (destructive && store.readOnly) { - return next(); - } - - return store[action].length > syncLength - ? store[action].apply(store, args.concat(next)) - : next(null, store[action].apply(store, args)); - } - - if (callback) { - return async.forEach(Object.keys(this.stores), runAction, function (err) { - return err ? callback(err) : callback(); - }); - } - - - Object.keys(this.stores).forEach(function (name) { - if (typeof response === 'undefined') { - var store = self.stores[name]; - - if (destructive && store.readOnly) { - return; - } - - response = store[action].apply(store, args); - - // Merge objects if necessary - if (response && action === 'get' && typeof response === 'object' && !Array.isArray(response)) { - mergeObjs.push(response); - response = undefined; - } - } - }); - - if (mergeObjs.length) { - response = common.merge(mergeObjs.reverse()); - } - - return response; -} - // // Throw the `err` if a callback is not supplied // @@ -560,6 +401,5 @@ function onError(err, callback) { if (callback) { return callback(err); } - throw err; } diff --git a/lib/nconf/stores/argv.js b/lib/nconf/stores/argv.js index 3841f7db..377846e3 100644 --- a/lib/nconf/stores/argv.js +++ b/lib/nconf/stores/argv.js @@ -6,7 +6,7 @@ */ var util = require('util'), - Memory = require('./memory').Memory; + Memory = require('./memory'); // // ### function Argv (options) @@ -14,7 +14,7 @@ var util = require('util'), // Constructor function for the Argv nconf store, a simple abstraction // around the Memory store that can read command-line arguments. // -var Argv = exports.Argv = function (options) { +var Argv = module.exports = function (options) { Memory.call(this, options); this.type = 'argv'; @@ -43,9 +43,11 @@ Argv.prototype.loadArgv = function () { var self = this, argv; + var optimist = require('optimist')(process.argv.slice(2)); + argv = typeof this.options === 'object' - ? require('optimist')(process.argv.slice(2)).options(this.options).argv - : require('optimist')(process.argv.slice(2)).argv; + ? optimist.options(this.options).argv + : optimist.argv; if (!argv) { return; diff --git a/lib/nconf/stores/env.js b/lib/nconf/stores/env.js index e73026ef..a7fda587 100644 --- a/lib/nconf/stores/env.js +++ b/lib/nconf/stores/env.js @@ -7,7 +7,7 @@ var util = require('util'), common = require('../common'), - Memory = require('./memory').Memory; + Memory = require('./memory'); // // ### function Env (options) @@ -15,7 +15,7 @@ var util = require('util'), // Constructor function for the Env nconf store, a simple abstraction // around the Memory store that can read process environment variables. // -var Env = exports.Env = function (options) { +var Env = module.exports = function (options) { Memory.call(this, options); options = options || {}; diff --git a/lib/nconf/stores/file.js b/lib/nconf/stores/file.js index 117f5ab8..3a998c5a 100644 --- a/lib/nconf/stores/file.js +++ b/lib/nconf/stores/file.js @@ -9,7 +9,7 @@ var fs = require('fs'), path = require('path'), util = require('util'), formats = require('../formats'), - Memory = require('./memory').Memory, + Memory = require('./memory'), exists = fs.exists || path.exists, existsSync = fs.existsSync || path.existsSync; @@ -19,7 +19,7 @@ var fs = require('fs'), // Constructor function for the File nconf store, a simple abstraction // around the Memory store that can persist configuration to disk. // -var File = exports.File = function (options) { +var File = module.exports = function (options) { if (!options || !options.file) { throw new Error ('Missing required option `file`'); } @@ -52,9 +52,16 @@ File.prototype.save = function (value, callback) { callback = value; value = null; } + console.error(value, callback, 777) + if (!callback) { + this.saveSync(); + return; + } + var self = this; fs.writeFile(this.file, this.format.stringify(this.store, null, this.json_spacing), function (err) { - return err ? callback(err) : callback(); + console.error('written', err, self.store) + return err ? callback(err, null) : callback(null, self.store); }); }; diff --git a/lib/nconf/stores/http.js b/lib/nconf/stores/http.js new file mode 100644 index 00000000..4bfc22df --- /dev/null +++ b/lib/nconf/stores/http.js @@ -0,0 +1,124 @@ +/* + * http.js: Simple http storage engine for nconf files + * + * (C) 2011, Nodejitsu Inc. + * + */ + +var http = require('http'), + url = require('url'); + util = require('util'), + formats = require('../formats'), + Memory = require('./memory'); + +// +// ### function Http (options) +// #### @options {Object} Options for this instance +// Constructor function for the Http nconf store, a simple abstraction +// around the Memory store that can persist configuration to disk. +// +var Http = module.exports = function (options) { + if (!options || !options.url) { + throw new Error ('Missing required option `url`'); + } + + Memory.call(this, options); + + this.type = 'http'; + this.url = options.url; + this.poll = options.poll || false; + this.format = options.format || formats.json; + this.interval = options.interval || 60 * 1000; + + if (this.poll) { + setInterval(this.load.bind(this, function () {}), this.interval); + } +}; + +// Inherit from the Memory store +util.inherits(Http, Memory); + +// +// ### function save (value, callback) +// #### @value {Object} _Ignored_ Left here for consistency +// #### @callback {function} Continuation to respond to when complete. +// Saves the current configuration object to disk at `this.file` +// using the format specified by `this.format`. +// +Http.prototype.save = function (value, callback) { + if (!callback) { + callback = value; + value = null; + } + + var self = this; + var options = url.parse(self.url); + options.method = 'put'; + var req = http.request(options); + var done = false; + req.on('error', function (e) { + if (done) return; + done = true; + callback(e, null); + }); + req.on('response', function (res) { + if (done) return; + done = true; + if (res.statusCode > 299 || res.statusCode < 200) { + callback(new Error('Status code ' + res.statusCode), null); + } + else { + callback(null, self.store); + } + }); + req.write(JSON.stringify(self.store)); + req.end(); +}; + +// +// ### function load (callback) +// #### @callback {function} Continuation to respond to when complete. +// Responds with an Object representing all keys associated in this instance. +// +Http.prototype.load = function (callback) { + var self = this; + var options = url.parse(self.url); + var req = http.request(options); + var done = false; + req.on('error', function (e) { + if (done) return; + done = true; + callback(e, null); + }); + req.on('response', function (res) { + if (res.statusCode > 299 || res.statusCode < 200) { + done = true; + callback(new Error('Status code ' + res.statusCode), null); + return; + } + else { + var store = ''; + res.on('error', function (e) { + if (done) return; + done = true; + callback(e, null); + }); + res.on('data', function (data) { + store += data; + }); + res.on('end', function () { + if (done) return; + done = true; + try { + self.store = self.format.parse(store); + } + catch (e) { + callback(e, null); + return; + } + callback(null, self.store); + }); + } + }); + req.end(); +}; diff --git a/lib/nconf/stores/literal.js b/lib/nconf/stores/literal.js index c7c1752c..b42022d8 100644 --- a/lib/nconf/stores/literal.js +++ b/lib/nconf/stores/literal.js @@ -6,9 +6,9 @@ */ var util = require('util'), - Memory = require('./memory').Memory + Memory = require('./memory') -var Literal = exports.Literal = function Literal (options) { +var Literal = module.exports = function Literal (options) { Memory.call(this, options); options = options || {} diff --git a/lib/nconf/stores/memory.js b/lib/nconf/stores/memory.js index b53ac12c..65960e9b 100644 --- a/lib/nconf/stores/memory.js +++ b/lib/nconf/stores/memory.js @@ -6,6 +6,7 @@ */ var common = require('../common'); +var merge = require('merge-recursive'); // // ### function Memory (options) @@ -15,17 +16,12 @@ var common = require('../common'); // // e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }` // -var Memory = exports.Memory = function (options) { +var Memory = module.exports = function (options) { options = options || {}; this.type = 'memory'; this.store = {}; - this.mtimes = {}; this.readOnly = false; this.loadFrom = options.loadFrom || null; - - if (this.loadFrom) { - this.store = common.loadFilesSync(this.loadFrom); - } }; // @@ -34,22 +30,8 @@ var Memory = exports.Memory = function (options) { // Retrieves the value for the specified key (if any). // Memory.prototype.get = function (key) { - var target = this.store, - path = common.path(key); - - // - // Scope into the object to get the appropriate nested context - // - while (path.length > 0) { - key = path.shift(); - if (target && key in target) { - target = target[key]; - continue; - } - return undefined; - } - - return target; + var value = common.scope(common.path(key), this.store); + return value; }; // @@ -63,13 +45,10 @@ Memory.prototype.set = function (key, value) { return false; } - var target = this.store, - path = common.path(key); - - if (path.length === 0) { - // - // Root must be an object - // + // + // Root must be an object + // + if (key == null) { if (!value || typeof value !== 'object') { return false; } @@ -79,27 +58,10 @@ Memory.prototype.set = function (key, value) { return true; } } - - // - // Update the `mtime` (modified time) of the key - // - this.mtimes[key] = Date.now(); - - // - // Scope into the object to get the appropriate nested context - // - while (path.length > 1) { - key = path.shift(); - if (!target[key] || typeof target[key] !== 'object') { - target[key] = {}; - } - - target = target[key]; - } - - // Set the specified value in the nested JSON structure - key = path.shift(); - target[key] = value; + var path = common.path(key); + var attribute = path.pop(); + var target = common.ensure(path, this.store); + target[attribute] = value; return true; }; @@ -113,31 +75,15 @@ Memory.prototype.clear = function (key) { return false; } - var target = this.store, - value = target, - path = common.path(key); - - // - // Remove the key from the set of `mtimes` (modified times) - // - delete this.mtimes[key]; + var path = common.path(key), + attribute = path.pop(), + target = common.scope(path, this.store); - // - // Scope into the object to get the appropriate nested context - // - for (var i = 0; i < path.length - 1; i++) { - key = path[i]; - value = target[key]; - if (typeof value !== 'function' && typeof value !== 'object') { - return false; - } - target = value; + if (target && typeof target === 'object') { + delete target[attribute]; + return true; } - - // Delete the key from the nested JSON structure - key = path[i]; - delete target[key]; - return true; + return false; }; // @@ -161,44 +107,23 @@ Memory.prototype.merge = function (key, value) { return this.set(key, value); } - var self = this, - target = this.store, - path = common.path(key), - fullKey = key; - - // - // Update the `mtime` (modified time) of the key - // - this.mtimes[key] = Date.now(); - - // - // Scope into the object to get the appropriate nested context - // - while (path.length > 1) { - key = path.shift(); - if (!target[key]) { - target[key] = {}; - } - - target = target[key]; - } - - // Set the specified value in the nested JSON structure - key = path.shift(); - + var self = this, + path = common.path(key), + attribute = path.pop(), + target = common.ensure(path, this.store); + // // If the current value at the key target is not an `Object`, // or is an `Array` then simply override it because the new value // is an Object. // - if (typeof target[key] !== 'object' || Array.isArray(target[key])) { - target[key] = value; + if (target[attribute] == null || typeof target[attribute] !== 'object' || Array.isArray(target[attribute])) { + target[attribute] = value; return true; } - - return Object.keys(value).every(function (nested) { - return self.merge(common.key(fullKey, nested), value[nested]); - }); + + merge.recursive(target[attribute], value); + return target; }; // @@ -209,12 +134,31 @@ Memory.prototype.reset = function () { if (this.readOnly) { return false; } - - this.mtimes = {}; this.store = {}; return true; }; +Memory.prototype.save = function (callback) { + var store = this.store; + if (this.saveSync) { + store = this.saveSync(); + } + if (callback) callback(null, store); + return store; +} + +Memory.prototype.saveSync = function (callback) { + return this.store; +} + +Memory.prototype.load = function (callback) { + var store = this.store; + if (this.loadSync) { + store = this.loadSync(); + } + if (callback) callback(null, store); +} + // // ### function loadSync // Returns the store managed by this instance diff --git a/package.json b/package.json index bcbb098a..d81a3c37 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "async": "0.1.x", "ini": "1.x.x", "optimist": "0.3.x", - "pkginfo": "0.2.x" + "pkginfo": "0.2.x", + "merge-recursive": "0.0.3" }, "devDependencies": { "vows": "0.6.x" @@ -32,4 +33,3 @@ "node": ">= 0.4.0" } } - diff --git a/test/common-test.js b/test/common-test.js index 9924372b..c25caac0 100644 --- a/test/common-test.js +++ b/test/common-test.js @@ -19,13 +19,13 @@ vows.describe('nconf/common').addBatch({ "Using nconf.common module": { "the loadFiles() method": { topic: function () { - nconf.loadFiles(files, this.callback); + nconf.common.loadFiles(files, this.callback); }, "should merge the files correctly": helpers.assertMerged }, "the loadFilesSync() method": { "should merge the files correctly": function () { - helpers.assertMerged(null, nconf.loadFilesSync(files)); + helpers.assertMerged(null, nconf.common.loadFilesSync(files)); } } } diff --git a/test/complete-test.js b/test/complete-test.js index 6ac8ef73..20e8e0c8 100644 --- a/test/complete-test.js +++ b/test/complete-test.js @@ -61,7 +61,7 @@ vows.describe('nconf/multiple-stores').addBatch({ "and saving *synchronously*": { topic: function () { nconf.set('weebls', 'stuff'); - return nconf.save(); + return nconf.saveSync(); }, "correct return value": function (topic) { Object.keys(topic).forEach(function (key) { @@ -93,6 +93,7 @@ vows.describe('nconf/multiple-stores').addBatch({ "and saving *asynchronously*": { topic: function () { nconf.set('weebls', 'crap'); + console.error('SAVING') nconf.save(this.callback); }, "correct return value": function (err, data) { @@ -103,24 +104,26 @@ vows.describe('nconf/multiple-stores').addBatch({ }, "the file": { topic: function () { - fs.readFile(completeTest, 'utf8', this.callback); + return fs.readFileSync(completeTest, 'utf8'); }, "saved correctly": function (err, data) { assert.isNull(err); + console.error(data+'') data = JSON.parse(data); Object.keys(data).forEach(function (key) { assert.deepEqual(nconf.get(key), data[key]); }); assert.equal(nconf.get('weebls'), 'crap'); + }, + teardown: function () { + console.error(completeTest) + fs.unlinkSync(completeTest); + nconf.remove('file'); + nconf.remove('memory'); + nconf.remove('argv'); + nconf.remove('env'); } } - }, - teardown: function () { - fs.unlinkSync(completeTest); - nconf.remove('file'); - nconf.remove('memory'); - nconf.remove('argv'); - nconf.remove('env'); } } }).export(module); \ No newline at end of file diff --git a/test/helpers.js b/test/helpers.js index f8504ed6..95ecd7ba 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -16,7 +16,7 @@ exports.assertMerged = function (err, merged) { merged = merged instanceof nconf.Provider ? merged.store.store : merged; - + assert.isNull(err); assert.isObject(merged); assert.isTrue(merged.apples); diff --git a/test/hierarchy-test.js b/test/hierarchy-test.js index 80e11e9f..592a5f22 100644 --- a/test/hierarchy-test.js +++ b/test/hierarchy-test.js @@ -26,6 +26,7 @@ vows.describe('nconf/hierarchy').addBatch({ return nconf; }, "should have the appropriate keys present": function () { + console.error(nconf.stores, nconf.get('title')) assert.equal(nconf.get('title'), 'My specific title'); assert.equal(nconf.get('color'), 'green'); assert.equal(nconf.get('movie'), 'Kill Bill');