From da2da7aea83e5d23ce3c75bdbfb2df3d96ea3028 Mon Sep 17 00:00:00 2001 From: indexzero Date: Fri, 16 Sep 2011 06:49:47 -0400 Subject: [PATCH] [api test breaking refactor] Significant refactor to how nconf works. Now a fully hierarchical configuration storage mechanism capable of multiple levels of stores of the same type. --- lib/nconf.js | 35 +--- lib/nconf/common.js | 92 ++++++++-- lib/nconf/provider.js | 365 ++++++++++++++++++++++++++----------- lib/nconf/stores.js | 20 +- lib/nconf/stores/memory.js | 40 +++- lib/nconf/stores/system.js | 91 ++++++--- test/common-test.js | 32 ++++ test/helpers.js | 40 ++++ test/nconf-test.js | 2 +- test/provider-test.js | 52 ++---- usage.js | 15 ++ 11 files changed, 550 insertions(+), 234 deletions(-) create mode 100644 test/common-test.js create mode 100644 test/helpers.js diff --git a/lib/nconf.js b/lib/nconf.js index f00e966..bc9646c 100644 --- a/lib/nconf.js +++ b/lib/nconf.js @@ -9,39 +9,20 @@ var fs = require('fs'), async = require('async'), common = require('./nconf/common'), Provider = require('./nconf/provider').Provider, - nconf = module.exports = Object.create(Provider.prototype); - -// -// Use the memory engine by default. -// -nconf.use('memory'); + nconf = module.exports = new Provider(); // // Expose the version from the package.json using `pkginfo`. // require('pkginfo')(module, 'version'); -// -// ### function path (key) -// #### @key {string} The ':' delimited key to split -// Returns a fully-qualified path to a nested nconf key. -// -nconf.path = function (key) { - return key.split(':'); -}; - -// -// ### function key (arguments) -// Returns a `:` joined string from the `arguments`. -// -nconf.key = function () { - return Array.prototype.slice.call(arguments).join(':'); -}; - // // Expose the various components included with nconf // -nconf.loadFiles = common.loadFiles; -nconf.formats = require('./nconf/formats'); -nconf.stores = require('./nconf/stores'); -nconf.Provider = Provider; \ No newline at end of file +nconf.key = common.key; +nconf.path = common.path; +nconf.loadFiles = common.loadFiles; +nconf.loadFilesSync = common.loadFilesSync; +nconf.formats = require('./nconf/formats'); +nconf.stores = require('./nconf/stores'); +nconf.Provider = Provider; \ No newline at end of file diff --git a/lib/nconf/common.js b/lib/nconf/common.js index 1ba183a..dedd08c 100644 --- a/lib/nconf/common.js +++ b/lib/nconf/common.js @@ -8,12 +8,29 @@ var fs = require('fs'), async = require('async'), formats = require('./formats'), - stores = require('./stores'); + Memory = require('./stores/Memory').Memory; var common = exports; // -// ### function loadFiles (files) +// ### function path (key) +// #### @key {string} The ':' delimited key to split +// Returns a fully-qualified path to a nested nconf key. +// +common.path = function (key) { + return key.split(':'); +}; + +// +// ### function key (arguments) +// Returns a `:` joined string from the `arguments`. +// +common.key = function () { + return Array.prototype.slice.call(arguments).join(':'); +}; + +// +// ### function loadFiles (files, callback) // #### @files {Object|Array} List of files (or settings object) to load. // #### @callback {function} Continuation to respond to when complete. // Loads all the data in the specified `files`. @@ -23,8 +40,7 @@ common.loadFiles = function (files, callback) { return callback(null, {}); } - var options = Array.isArray(files) ? { files: files } : files, - store = new stores.Memory(); + var options = Array.isArray(files) ? { files: files } : files; // // Set the default JSON format if not already @@ -32,22 +48,64 @@ common.loadFiles = function (files, callback) { // options.format = options.format || formats.json; - function loadFile (file, next) { + function parseFile (file, next) { fs.readFile(file, function (err, data) { - if (err) { - return next(err); - } - - data = options.format.parse(data.toString()); - Object.keys(data).forEach(function (key) { - store.merge(key, data[key]); - }); - - next(); + return !err + ? next(null, options.format.parse(data.toString())) + : next(err); }); } - async.forEach(files, loadFile, function (err) { - return err ? callback(err) : callback(null, store.store); + async.map(files, parseFile, function (err, objs) { + return err ? callback(err) : callback(null, common.merge(objs)); }); +}; + +// +// ### function loadFilesSync (files) +// #### @files {Object|Array} List of files (or settings object) to load. +// Loads all the data in the specified `files` synchronously. +// +common.loadFilesSync = function (files) { + if (!files) { + return; + } + + // + // Set the default JSON format if not already + // specified + // + var options = Array.isArray(files) ? { files: files } : files; + options.format = options.format || formats.json; + + return common.merge(files.map(function (file) { + return options.format.parse(fs.readFileSync(file, 'utf8')); + })); +}; + +// +// ### function merge (objs) +// #### @objs {Array} Array of object literals to merge +// Merges the specified `objs` using a temporary instance +// of `stores.Memory`. +// +common.merge = function (objs) { + var store = new Memory(); + + objs.forEach(function (obj) { + Object.keys(obj).forEach(function (key) { + store.merge(key, obj[key]); + }); + }); + + 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); }; \ No newline at end of file diff --git a/lib/nconf/provider.js b/lib/nconf/provider.js index f4a4076..83b0b5d 100644 --- a/lib/nconf/provider.js +++ b/lib/nconf/provider.js @@ -9,6 +9,8 @@ var async = require('async'), common = require('./common'), stores = require('./stores'); + + // // ### function Provider (options) // #### @options {Object} Options for this instance. @@ -16,38 +18,137 @@ var async = require('async'), // for exposing the pluggable storage features of `nconf`. // var Provider = exports.Provider = function (options) { - options = options || {}; - this.overrides = options.overrides || null; - this.useArgv = options.useArgv || false; - this.useEnv = options.useEnv || false; - this.store = stores.create(options.type || 'memory', options); + var self = this; + + // + // Setup default options for working with `stores`, + // `overrides`, `process.env` and `process.argv`. + // + options = options || {}; + this._overrides = options.overrides || null; + this._argv = options.argv || false; + this._envĀ  = options.env || false; + this._reserved = Object.keys(Provider.prototype); + this._stores = []; + + // + // Add the default `system` store for working with + // `overrides`, `process.env`, `process.argv` and + // a simple in-memory objects. + // + this.add('system', options); + + if (options.type) { + this.add(options.type, options); + } + else if (options.store) { + this.add(options.store.name || options.store.type, options.store); + } + else if (options.stores) { + Object.keys(options.stores).forEach(function (store) { + self.add(store.name || store.type, store); + }); + } }; // -// ### function use (type, options) +// ### function use (name, options) // #### @type {string} Type of the nconf store to use. // #### @options {Object} Options for the store instance. -// Sets the active `this.store` to a new instance of the -// specified `type`. +// Adds (or replaces) a new store with the specified `name` +// and `options`. If `options.type` is not set, then `name` +// will be used instead: // -Provider.prototype.use = function (type, options) { - var self = this; - options = options || {}; +// provider.use('file'); +// provider.use('file', { type: 'file', filename: '/path/to/userconf' }) +// +Provider.prototype.use = function (name, options) { + if (name === 'system') { + return; + } + else if (this._reserved.indexOf(name) !== -1) { + throw new Error('Cannot use reserved name: ' + name); + } + + options = options || {}; + options.type = options.type || name; - function sameOptions () { + function sameOptions (store) { return Object.keys(options).every(function (key) { - return options[key] === self.store[key]; + return options[key] === store[key]; }); } - if (!this.store || type.toLowerCase() !== this.store.type - || !sameOptions()) { - this.store = stores.create(type, options); + var store = this[name], + update = store && !sameOptions(store); + + if (!store || update) { + if (update) { + this.remove(name); + } + + this.add(name, options); } return this; }; +// +// ### function add (name, options) +// #### @name {string} Name of the store to add to this instance +// #### @options {Object} Options for the store to create +// Adds a new store with the specified `name` and `options`. If `options.type` +// is not set, then `name` will be used instead: +// +// provider.add('memory'); +// provider.add('userconf', { type: 'file', filename: '/path/to/userconf' }) +// +Provider.prototype.add = function (name, options) { + if (this._reserved.indexOf(name) !== -1) { + throw new Error('Cannot use reserved name: ' + name); + } + + options = options || {}; + options.type = options.type || name; + + if (Object.keys(stores).indexOf(common.capitalize(options.type)) === -1) { + throw new Error('Cannot add store with unknown type: ' + options.type); + } + + this[name] = this.create(options.type, options); + this._stores.push(name); +}; + +// +// ### function remove (name) +// #### @name {string} Name of the store to remove from this instance +// Removes a store with the specified `name` from this instance. Users +// are allowed to pass in a type argument (e.g. `memory`) as name if +// this was used in the call to `.add()`. +// +Provider.prototype.remove = function (name) { + if (this._reserved.indexOf(name) !== -1) { + throw new Error('Cannot use reserved name: ' + name); + } + else if (!this[name]) { + throw new Error('Cannot remove store that does not exist: ' + name); + } + + delete this[name]; + this._stores.splice(this._stores.indexOf(name), 1); +}; + +// +// ### 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 stores[common.capitalize(type.toLowerCase())](options); +}; + // // ### function get (key, callback) // #### @key {string} Key to retrieve for this instance. @@ -55,14 +156,6 @@ Provider.prototype.use = function (type, options) { // Retrieves the value for the specified key (if any). // Provider.prototype.get = function (key, callback) { - if (this.overrides && Object.prototype.hasOwnProperty.call(this.overrides, key)) { - if (callback) { - callback(null, this.overrides[key]); - } - - return this.overrides[key]; - } - return this._execute('get', 1, key, callback); }; @@ -77,6 +170,25 @@ Provider.prototype.set = function (key, value, callback) { return this._execute('set', 2, key, value, callback); }; +// +// ### function reset (callback) +// #### @callback {function} **Optional** Continuation to respond to when complete. +// Clears all keys associated with this instance. +// +Provider.prototype.reset = function (callback) { + return this._execute('reset', 0, callback); +}; + +// +// ### function clear (key, callback) +// #### @key {string} Key to remove from this instance +// #### @callback {function} **Optional** Continuation to respond to when complete. +// Removes the value for the specified `key` from this instance. +// +Provider.prototype.clear = function (key, callback) { + return this._execute('clear', 1, key, callback); +}; + // // ### function merge ([key,] value [, callback]) // #### @key {string} Key to merge the value into @@ -109,51 +221,48 @@ Provider.prototype.merge = function () { return this._execute('merge', 2, key, value, callback); }; -// -// ### function mergeFiles (files, callback) -// #### @files {Object|Array} List of files (or settings object) to load. -// #### @callback {function} Continuation to respond to when complete. -// Merges all `key:value` pairs in the `files` supplied into the -// store that is managed by this provider instance. -// -Provider.prototype.mergeFiles = function (files, callback) { - var self = this; - - common.loadFiles(files, function (err, merged) { - return !err - ? self.merge(merged, callback) - : onError(err); - }); -}; - -// -// ### function clear (key, callback) -// #### @key {string} Key to remove from this instance -// #### @callback {function} **Optional** Continuation to respond to when complete. -// Removes the value for the specified `key` from this instance. -// -Provider.prototype.clear = function (key, callback) { - return this._execute('clear', 1, key, callback); -}; - // // ### function load (callback) // #### @callback {function} Continuation to respond to when complete. // Responds with an Object representing all keys associated in this instance. // Provider.prototype.load = function (callback) { + var self = this; + + function loadStoreSync(name) { + var store = self[name]; + + if (!store.loadSync) { + throw new Error('nconf store ' + store.type + ' has no loadSync() method'); + } + + return store.loadSync(); + } + + function loadStore(name, next) { + var store = self[name]; + + if (!store.load && !store.loadSync) { + return next(new Error('nconf store ' + store.type + ' has no load() method')); + } + + return store.loadSync + ? next(null, store.loadSync()) + : store.load(next); + } + // // If we don't have a callback and the current // store is capable of loading synchronously // then do so. // - if (!callback && this.store.loadSync) { - return this.store.loadSync(); + if (!callback) { + return common.merge(this._stores.map(loadStoreSync)); } - return !this.store.load - ? onError(new Error('nconf store ' + this.store.type + ' has no load() method'), callback) - : this.store.load(callback); + async.map(this._stores, loadStore, function (err, objs) { + return err ? callback(err) : callback(null, common.merge(objs)); + }); }; // @@ -164,32 +273,47 @@ Provider.prototype.load = function (callback) { // instance and then adds all key-value pairs in `value`. // Provider.prototype.save = function (value, callback) { - if (!callback) { + if (!callback && typeof value === 'function') { callback = value; value = null; - - // - // If we still don't have a callback and the - // current store is capable of saving synchronously - // then do so. - // - if (!callback && this.store.saveSync) { - return this.store.saveSync(); - } } - return !this.store.save - ? onError(new Error('nconf store ' + this.store.type + ' has no save() method'), callback) - : this.store.save(value, callback); -}; - -// -// ### function reset (callback) -// #### @callback {function} **Optional** Continuation to respond to when complete. -// Clears all keys associated with this instance. -// -Provider.prototype.reset = function (callback) { - return this._execute('reset', 0, callback); + var self = this; + + function saveStoreSync(name) { + var store = self[name]; + + if (!store.saveSync) { + throw new Error('nconf store ' + store.type + ' has no saveSync() method'); + } + + return store.saveSync(); + } + + function saveStore(name, next) { + var store = self[name]; + + if (!store.load && !store.saveSync) { + return next(new Error('nconf store ' + store.type + ' has no save() method')); + } + + return store.saveSync + ? next(null, store.saveSync()) + : store.save(next); + } + + // + // If we don't have a callback and the current + // store is capable of saving synchronously + // then do so. + // + if (!callback) { + return common.merge(this._stores.map(saveStoreSync)); + } + + async.map(this._stores, saveStore, function (err, objs) { + return err ? callback(err) : callback(); + }); }; // @@ -197,49 +321,67 @@ Provider.prototype.reset = function (callback) { // #### @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 `this.store`, ensuring a callback supplied +// 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, + callback = typeof args[args.length - 1] === 'function' && args.pop(), + self = this, response; - - if (this.store[action].length > syncLength) { - return this.store[action].apply(this.store, args); - } - callback = typeof args[args.length - 1] === 'function' && args.pop(); - response = this.store[action].apply(this.store, args); + function runAction (name, next) { + var store = self[name] + + return store[action].length > syncLength + ? store[action].apply(store, args.concat(next)) + : next(null, store[action].apply(store, args)); + } if (callback) { - callback(null, response); + return async.forEach(self._stores, runAction, function (err) { + return err ? callback(err) : callback(); + }); } - + + this._stores.forEach(function (name) { + var store = self[name]; + response = store[action].apply(store, args); + }); + return response; } // -// ### getter @useArgv {boolean} -// Gets a property indicating if we should wrap calls to `.get` -// by checking `optimist.argv`. +// ### @argv {boolean} +// Gets or sets a property representing overrides which supercede all +// other values for this instance. // -Provider.prototype.__defineGetter__('useArgv', function () { - return this._useArgv; +Provider.prototype.__defineSetter__('overrides', updateSystem.bind(this, 'overrides')); +Provider.prototype.__defineGetter__('overrides', function () { + return this._argv; }); // -// ### setter @useArgv {boolean} -// Sets a property indicating if we should wrap calls to `.get` -// by checking `optimist.argv`. +// ### @argv {boolean} +// Gets or sets a property indicating if we should wrap calls to `.get` +// by checking `optimist.argv`. Can be a boolean or the pass-thru +// options for `optimist`. // -Provider.prototype.__defineSetter__('useArgv', function (val) { - this._useArgv = val || false; - - if (this._useArgv) { - this._argv = this._argv || require('optimist').argv; - this.overrides = this.overrides || this._argv; - } +Provider.prototype.__defineSetter__('argv', updateSystem.bind(this, 'argv')); +Provider.prototype.__defineGetter__('argv', function () { + return this._argv; +}); + +// +// ### @env {boolean} +// Gets or sets a property indicating if we should wrap calls to `.get` +// by checking `process.env`. Can be a boolean or an Array of +// environment variables to extract. +// +Provider.prototype.__defineSetter__('env', updateSystem.bind(this, 'env')); +Provider.prototype.__defineGetter__('env', function () { + return this._env; }); // @@ -251,4 +393,21 @@ function onError(err, callback) { } throw err; +} + +// +// Helper function for working with the +// default `system` store for providers. +// +function updateSystem(prop, value) { + var system = this['system']; + + if (system[prop] === value) { + return; + } + + value = value || false; + this['_' + prop] = value; + system[prop] = value; + system.loadSync(); } \ No newline at end of file diff --git a/lib/nconf/stores.js b/lib/nconf/stores.js index e80f2e9..b44b3b6 100644 --- a/lib/nconf/stores.js +++ b/lib/nconf/stores.js @@ -6,31 +6,17 @@ */ var fs = require('fs'), + common = require('./common'), stores = exports; -function capitalize (str) { - return str && str[0].toUpperCase() + str.slice(1); -}; - // // Setup all stores as lazy-loaded getters. // fs.readdirSync(__dirname + '/stores').forEach(function (file) { var store = file.replace('.js', ''), - name = capitalize(store); + name = common.capitalize(store); stores.__defineGetter__(name, function () { return require('./stores/' + store)[name]; }); -}); - -// -// ### 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`. -// -stores.create = function (type, options) { - return new stores[capitalize(type.toLowerCase())](options); -}; \ No newline at end of file +}); \ No newline at end of file diff --git a/lib/nconf/stores/memory.js b/lib/nconf/stores/memory.js index 94e234e..887e228 100644 --- a/lib/nconf/stores/memory.js +++ b/lib/nconf/stores/memory.js @@ -5,7 +5,7 @@ * */ -var nconf = require('../../nconf'); +var common = require('../common'); // // ### function Memory (options) @@ -16,10 +16,16 @@ var nconf = require('../../nconf'); // e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }` // var Memory = exports.Memory = function (options) { - options = options || {}; - this.type = 'memory'; - this.store = {}; - this.mtimes = {}; + 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); + } }; // @@ -29,7 +35,7 @@ var Memory = exports.Memory = function (options) { // Memory.prototype.get = function (key) { var target = this.store, - path = nconf.path(key); + path = common.path(key); // // Scope into the object to get the appropriate nested context @@ -54,8 +60,12 @@ Memory.prototype.get = function (key) { // Sets the `value` for the specified `key` in this instance. // Memory.prototype.set = function (key, value) { + if (this.readOnly) { + return false; + } + var target = this.store, - path = nconf.path(key); + path = common.path(key); // // Update the `mtime` (modified time) of the key @@ -86,8 +96,12 @@ Memory.prototype.set = function (key, value) { // Removes the value for the specified `key` from this instance. // Memory.prototype.clear = function (key) { + if (this.readOnly) { + return false; + } + var target = this.store, - path = nconf.path(key); + path = common.path(key); // // Remove the key from the set of `mtimes` (modified times) @@ -121,6 +135,10 @@ Memory.prototype.clear = function (key) { // completely overwritten. // Memory.prototype.merge = function (key, value) { + if (this.readOnly) { + return false; + } + // // If the key is not an `Object` or is an `Array`, // then simply set it. Merging is for Objects. @@ -131,7 +149,7 @@ Memory.prototype.merge = function (key, value) { var self = this, target = this.store, - path = nconf.path(key), + path = common.path(key), fullKey = key; // @@ -174,6 +192,10 @@ Memory.prototype.merge = function (key, value) { // Clears all keys associated with this instance. // Memory.prototype.reset = function () { + if (this.readOnly) { + return false; + } + this.mtimes = {}; this.store = {}; return true; diff --git a/lib/nconf/stores/system.js b/lib/nconf/stores/system.js index e2f599e..764a3a1 100644 --- a/lib/nconf/stores/system.js +++ b/lib/nconf/stores/system.js @@ -19,9 +19,11 @@ var util = require('util'), var System = exports.System = function (options) { Memory.call(this, options); - this.type = 'system'; - this.only = options.only || []; - this.argv = options.argv || {}; + this.type = 'system'; + this.readOnly = true; + this.overrides = options.overrides || null; + this.env = options.env || false; + this.argv = options.argv || false; }; // Inherit from the Memory store @@ -32,9 +34,65 @@ util.inherits(System, Memory); // Loads the data passed in from `process.env` into this instance. // System.prototype.loadSync = function () { - this.loadEnv(); - this.loadArgv(); + if (this.env) { + this.loadEnv(); + } + if (this.argv) { + this.loadArgv(); + } + + if (this.overrides) { + this.loadOverrides(); + } + + return this.store; +}; + +// +// ### function loadOverrides () +// Loads any overrides set on this instance into +// the underlying managed `Memory` store. +// +System.prototype.loadOverrides = function () { + if (!this.overrides) { + return; + } + + var self = this, + keys = Object.keys(this.overrides); + + keys.forEach(function (key) { + self.set(key, self.overrides[key]); + }); + + return this.store; +}; + +// +// ### function loadArgv () +// Loads the data passed in from the command-line arguments +// into this instance. +// +System.prototype.loadArgv = function () { + var self = this, + argv; + + if (typeof this.argv === 'object') { + argv = require('optimist').options(this.argv).argv; + } + else if (this.argv) { + argv = require('optimist').argv; + } + + if (!argv) { + return; + } + + Object.keys(argv).forEach(function (key) { + self.set(key, argv[key]); + }); + return this.store; }; @@ -45,28 +103,15 @@ System.prototype.loadSync = function () { System.prototype.loadEnv = function () { var self = this; + if (!this.env) { + return; + } + Object.keys(process.env).filter(function (key) { - return !self.only.length || self.only.indexOf(key) !== -1; + return !self.env.length || self.env.indexOf(key) !== -1; }).forEach(function (key) { self.set(key, process.env[key]); }); - return this.store; -}; - -// -// ### function loadSync () -// Loads the data passed in from the command-line arguments -// into this instance. -// -Argv.prototype.loadArgv = function () { - var self = this, argv = Object.keys(this.argv) > 0 - ? require('optimist').options(this.argv).argv - : require('optimist').argv; - - Object.keys(argv).forEach(function (key) { - self.set(key, argv[key]) - }); - return this.store; }; \ No newline at end of file diff --git a/test/common-test.js b/test/common-test.js new file mode 100644 index 0000000..b70ce2a --- /dev/null +++ b/test/common-test.js @@ -0,0 +1,32 @@ +/* + * common.js: Tests for common utility function in nconf. + * + * (C) 2011, Charlie Robbins + * + */ + +var fs = require('fs'), + path = require('path'), + vows = require('vows'), + assert = require('assert'), + helpers = require('./helpers'), + nconf = require('../lib/nconf'); + +var mergeDir = path.join(__dirname, 'fixtures', 'merge'), + files = fs.readdirSync(mergeDir).map(function (f) { return path.join(mergeDir, f) }); + +vows.describe('nconf/common').addBatch({ + "Using nconf.common module": { + "the loadFiles() method": { + topic: function () { + nconf.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)); + } + } + } +}).export(module); \ No newline at end of file diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 0000000..600e2b0 --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,40 @@ +/* + * helpers.js: Test helpers for nconf. + * + * (C) 2011, Charlie Robbins + * + */ + +var assert = require('assert'), + spawn = require('child_process').spawn, + nconf = require('../lib/nconf'); + +exports.assertMerged = function (err, merged) { + merged = merged instanceof nconf.Provider + ? merged.store.store + : merged; + + assert.isNull(err); + assert.isObject(merged); + assert.isTrue(merged.apples); + assert.isTrue(merged.bananas); + assert.isObject(merged.candy); + assert.isTrue(merged.candy.something1); + assert.isTrue(merged.candy.something2); + assert.isTrue(merged.candy.something3); + assert.isTrue(merged.candy.something4); + assert.isTrue(merged.dates); + assert.isTrue(merged.elderberries); +}; + +exports.assertDefaults = function (script) { + return { + topic: function () { + spawn('node', [script, '--something', 'foobar']) + .stdout.once('data', this.callback.bind(this, null)); + }, + "should respond with the value passed into the script": function (_, data) { + assert.equal(data.toString(), 'foobar'); + } + } +} \ No newline at end of file diff --git a/test/nconf-test.js b/test/nconf-test.js index 72c71a2..35ad55c 100644 --- a/test/nconf-test.js +++ b/test/nconf-test.js @@ -28,7 +28,7 @@ vows.describe('nconf').addBatch({ "the use() method": { "should instaniate the correct store": function () { nconf.use('memory'); - assert.instanceOf(nconf.store, nconf.stores.Memory); + assert.instanceOf(nconf.memory, nconf.stores.Memory); } }, "it should": { diff --git a/test/provider-test.js b/test/provider-test.js index 3f85837..3f604f0 100644 --- a/test/provider-test.js +++ b/test/provider-test.js @@ -10,60 +10,38 @@ var assert = require('assert'), path = require('path'), spawn = require('child_process').spawn, vows = require('vows'), + helpers = require('./helpers'), nconf = require('../lib/nconf'); -var mergeFixtures = path.join(__dirname, 'fixtures', 'merge'), +var fixturesDir = path.join(__dirname, 'fixtures'), + mergeFixtures = path.join(fixturesDir, 'merge'), files = [path.join(mergeFixtures, 'file1.json'), path.join(mergeFixtures, 'file2.json')], override = JSON.parse(fs.readFileSync(files[0]), 'utf8'), first = '/path/to/file1', second = '/path/to/file2'; -function assertDefaults (script) { - return { - topic: function () { - spawn('node', [script, '--something', 'foobar']) - .stdout.once('data', this.callback.bind(this, null)); - }, - "should respond with the value passed into the script": function (_, data) { - assert.equal(data.toString(), 'foobar'); - } - } -} - -function assertMerged (provider) { - var store = provider.store.store; - assert.isTrue(store.apples); - assert.isTrue(store.bananas); - assert.isTrue(store.candy.something1); - assert.isTrue(store.candy.something2); - assert.isTrue(store.candy.something3); - assert.isTrue(store.candy.something4); - assert.isTrue(store.dates); - assert.isTrue(store.elderberries); -} - vows.describe('nconf/provider').addBatch({ "When using nconf": { "an instance of 'nconf.Provider'": { "calling the use() method with the same store type and different options": { topic: new nconf.Provider().use('file', { file: first }), "should use a new instance of the store type": function (provider) { - var old = provider.store; + var old = provider.file; - assert.equal(provider.store.file, first); + assert.equal(provider.file.file, first); provider.use('file', { file: second }); - assert.notStrictEqual(old, provider.store); - assert.equal(provider.store.file, second); + assert.notStrictEqual(old, provider.file); + assert.equal(provider.file.file, second); } }, - "when 'useArgv' is true": assertDefaults(path.join(__dirname, 'fixtures', 'scripts', 'nconf-override.js')) + //"when 'useArgv' is true": helpers.assertDefaults(path.join(fixturesDir, 'scripts', 'nconf-override.js')) }, - "the default nconf provider": { - "when 'useArgv' is true": assertDefaults(path.join(__dirname, 'fixtures', 'scripts', 'default-override.js')) - } + /*"the default nconf provider": { + "when 'useArgv' is true": helpers.assertDefaults(path.join(fixturesDir, 'scripts', 'default-override.js')) + }*/ } -}).addBatch({ +})/*.addBatch({ "When using nconf": { "an instance of 'nconf.Provider'": { "the merge() method": { @@ -71,7 +49,7 @@ vows.describe('nconf/provider').addBatch({ "should have the result merged in": function (provider) { provider.load(); provider.merge(override); - assertMerged(provider); + helpers.assertMerged(null, provider); } }, "the mergeFiles() method": { @@ -80,9 +58,9 @@ vows.describe('nconf/provider').addBatch({ provider.mergeFiles(files, this.callback.bind(this, null, provider)) }, "should have the result merged in": function (_, provider) { - assertMerged(provider); + helpers.assertMerged(null, provider); } } } } -}).export(module); \ No newline at end of file +})*/.export(module); \ No newline at end of file diff --git a/usage.js b/usage.js index 0bc1d2f..662674f 100644 --- a/usage.js +++ b/usage.js @@ -2,6 +2,21 @@ var fs = require('fs'), path = require('path'), nconf = require('./lib/nconf'); +var single = new nconf.Provider({ + useEnv: true, + useArgv: true, + store: { + type: 'file', + file: path.join(__dirname, 'config.json') + } +}); + +var multiple = new nconf.Provider({ + stores: [ + { type: 'file', } + ] +}); + // // Setup nconf to use the 'file' store and set a couple of values; //