[refactor] Refactor to make using nconf more fluent.

This commit is contained in:
indexzero 2011-11-20 16:21:09 -05:00
parent 2c1ef7118c
commit c3c315d648
18 changed files with 267 additions and 293 deletions

View file

@ -25,20 +25,8 @@ var Provider = exports.Provider = function (options) {
// `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 = [];
this.sources = [];
//
// Add the default `system` store for working with
// `overrides`, `process.env`, `process.argv` and
// a simple in-memory objects.
//
this.add('system', options);
//
// Add any stores passed in through the options
// to this instance.
@ -55,20 +43,27 @@ var Provider = exports.Provider = function (options) {
self.add(store.name || name || store.type, store);
});
}
};
//
// Add any read-only sources to this instance
// Define wrapper functions for using basic stores
// in 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));
});
}
['argv', 'env', 'file'].forEach(function (type) {
Provider.prototype[type] = function (options) {
return this.add(type, options);
};
});
//
// Define wrapper functions for using
// overrides and defaults
//
['defaults', 'overrides'].forEach(function (type) {
Provider.prototype[type] = function (options) {
return this.add('literal', options);
}
});
//
// ### function use (name, options)
@ -82,13 +77,6 @@ var Provider = exports.Provider = function (options) {
// 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 || {};
var type = options.type || name;
@ -98,7 +86,7 @@ Provider.prototype.use = function (name, options) {
});
}
var store = this[name],
var store = this.sources[name],
update = store && !sameOptions(store);
if (!store || update) {
@ -123,10 +111,6 @@ Provider.prototype.use = function (name, options) {
// 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 || {};
var type = options.type || name;
@ -134,11 +118,10 @@ Provider.prototype.add = function (name, options) {
throw new Error('Cannot add store with unknown type: ' + type);
}
this[name] = this.create(type, options);
this._stores.push(name);
this.sources[name] = this.create(type, options);
if (this[name].loadSync) {
this[name].loadSync();
if (this.sources[name].loadSync) {
this.sources[name].loadSync();
}
return this;
@ -152,15 +135,7 @@ Provider.prototype.add = function (name, options) {
// 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);
delete this.sources[name];
return this;
};
@ -196,13 +171,14 @@ Provider.prototype.get = function (key, callback) {
// the entire set of stores, but up until there is a defined value.
//
var current = 0,
names = Object.keys(this.sources),
self = this,
response;
async.whilst(function () {
return typeof response === 'undefined' && current < self._stores.length;
return typeof response === 'undefined' && current < names.length;
}, function (next) {
var store = self[self._stores[current]];
var store = self.sources[names[current]];
current++;
if (store.get.length >= 2) {
@ -292,7 +268,7 @@ Provider.prototype.merge = function () {
//
Provider.prototype.load = function (callback) {
var self = this,
stores = this._stores.map(function (name) { return self[name] });
stores = Object.keys(this.sources).map(function (name) { return self.sources[name] });
function loadStoreSync(store) {
if (!store.loadSync) {
@ -373,10 +349,11 @@ Provider.prototype.save = function (value, callback) {
value = null;
}
var self = this;
var self = this,
names = Object.keys(this.sources);
function saveStoreSync(name) {
var store = self[name];
var store = self.sources[name];
if (!store.saveSync) {
throw new Error('nconf store ' + store.type + ' has no saveSync() method');
@ -386,7 +363,7 @@ Provider.prototype.save = function (value, callback) {
}
function saveStore(name, next) {
var store = self[name];
var store = self.sources[name];
if (!store.save && !store.saveSync) {
return next(new Error('nconf store ' + store.type + ' has no save() method'));
@ -403,10 +380,10 @@ Provider.prototype.save = function (value, callback) {
// then do so.
//
if (!callback) {
return common.merge(this._stores.map(saveStoreSync));
return common.merge(names.map(saveStoreSync));
}
async.map(this._stores, saveStore, function (err, objs) {
async.map(names, saveStore, function (err, objs) {
return err ? callback(err) : callback();
});
};
@ -426,7 +403,7 @@ Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
response;
function runAction (name, next) {
var store = self[name]
var store = self.sources[name];
return store[action].length > syncLength
? store[action].apply(store, args.concat(next))
@ -434,45 +411,22 @@ Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
}
if (callback) {
return async.forEach(self._stores, runAction, function (err) {
return async.forEach(Object.keys(self.sources), runAction, function (err) {
return err ? callback(err) : callback();
});
}
this._stores.forEach(function (name) {
var store = self[name];
Object.keys(this.sources).forEach(function (name) {
if (typeof response === 'undefined') {
var store = self.sources[name];
response = store[action].apply(store, args);
}
});
return response;
}
//
// ### @argv {boolean}
// Gets or sets a property representing overrides which supercede all
// other values for this instance.
//
Provider.prototype.__defineSetter__('overrides', function (val) { updateSystem.call(this, 'overrides', val) });
Provider.prototype.__defineGetter__('overrides', function () { return this._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__('argv', function (val) { updateSystem.call(this, 'argv', val) });
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', function (val) { updateSystem.call(this, 'env', val) });
Provider.prototype.__defineGetter__('env', function () { return this._env });
//
// Throw the `err` if a callback is not supplied
//
@ -483,20 +437,3 @@ 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();
}

59
lib/nconf/stores/argv.js Normal file
View file

@ -0,0 +1,59 @@
/*
* argv.js: Simple memory-based store for command-line arguments.
*
* (C) 2011, Charlie Robbins
*
*/
var util = require('util'),
optimist = require('optimist'),
Memory = require('./memory').Memory;
//
// ### function Argv (options)
// #### @options {Object} Options for this instance.
// 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) {
Memory.call(this, options);
this.type = 'argv';
this.options = options || false;
};
// Inherit from the Memory store
util.inherits(Argv, Memory);
//
// ### function loadSync ()
// Loads the data passed in from `process.argv` into this instance.
//
Argv.prototype.loadSync = function () {
this.loadArgv();
return this.store;
};
//
// ### function loadArgv ()
// Loads the data passed in from the command-line arguments
// into this instance.
//
Argv.prototype.loadArgv = function () {
var self = this,
argv;
argv = typeof this.options === 'object'
? optimist(process.argv.slice(2)).options(this.options).argv
: optimist(process.argv.slice(2)).argv;
if (!argv) {
return;
}
Object.keys(argv).forEach(function (key) {
self.set(key, argv[key]);
});
return this.store;
};

51
lib/nconf/stores/env.js Normal file
View file

@ -0,0 +1,51 @@
/*
* env.js: Simple memory-based store for environment variables
*
* (C) 2011, Charlie Robbins
*
*/
var util = require('util'),
Memory = require('./memory').Memory;
//
// ### function Env (options)
// #### @options {Object} Options for this instance.
// 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) {
Memory.call(this, options);
this.type = 'env';
this.options = options || [];
};
// Inherit from the Memory store
util.inherits(Env, Memory);
//
// ### function loadSync ()
// Loads the data passed in from `process.env` into this instance.
//
Env.prototype.loadSync = function () {
this.loadEnv();
return this.store;
};
//
// ### function loadEnv ()
// Loads the data passed in from `process.env` into this instance.
//
Env.prototype.loadEnv = function () {
var self = this;
Object.keys(process.env).filter(function (key) {
return !self.options.length || self.options.indexOf(key) !== -1;
}).forEach(function (key) {
self.set(key, process.env[key]);
});
return this.store;
};

View file

@ -0,0 +1,18 @@
/*
* literal.js: Simple literal Object store for nconf.
*
* (C) 2011, Charlie Robbins
*
*/
var util = require('util'),
Memory = require('./memory').Memory
var Literal = exports.Literal = function Literal (store) {
Memory.call(this);
this.type = 'literal'
this.store = store || {};
};
// Inherit from Memory store.
util.inherits(Literal, Memory);

View file

@ -1,118 +0,0 @@
/*
* system.js: Simple memory-based store for process environment variables and
* command-line arguments.
*
* (C) 2011, Charlie Robbins
*
*/
var util = require('util'),
Memory = require('./memory').Memory;
//
// ### function System (options)
// #### @options {Object} Options for this instance.
// Constructor function for the System nconf store, a simple abstraction
// around the Memory store that can read process environment variables
// and command-line arguments.
//
var System = exports.System = function (options) {
options = options || {};
Memory.call(this, options);
this.type = 'system';
this.overrides = options.overrides || null;
this.env = options.env || false;
this.argv = options.argv || false;
};
// Inherit from the Memory store
util.inherits(System, Memory);
//
// ### function loadSync ()
// Loads the data passed in from `process.env` into this instance.
//
System.prototype.loadSync = function () {
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')(process.argv.slice(2)).options(this.argv).argv;
}
else if (this.argv) {
argv = require('optimist')(process.argv.slice(2)).argv;
}
if (!argv) {
return;
}
Object.keys(argv).forEach(function (key) {
self.set(key, argv[key]);
});
return this.store;
};
//
// ### function loadEnv ()
// Loads the data passed in from `process.env` into this instance.
//
System.prototype.loadEnv = function () {
var self = this;
if (!this.env) {
return;
}
Object.keys(process.env).filter(function (key) {
return !self.env.length || self.env.indexOf(key) !== -1;
}).forEach(function (key) {
self.set(key, process.env[key]);
});
return this.store;
};

View file

@ -18,6 +18,6 @@
"vows": "0.5.x >=0.5.11"
},
"main": "./lib/nconf",
"scripts": { "test": "vows test/*-test.js --spec" },
"scripts": { "test": "vows test/*-test.js test/**/*-test.js --spec" },
"engines": { "node": ">= 0.4.0" }
}

View file

@ -5,7 +5,6 @@
*
*/
var nconf = require('../../../lib/nconf');
var nconf = require('../../../lib/nconf').argv().env();
nconf.argv = true;
process.stdout.write(nconf.get('something'));

View file

@ -5,14 +5,12 @@
*
*/
var nconf = require('../../../lib/nconf');
nconf.argv = true;
var nconf = require('../../../lib/nconf').argv();
//
// Remove 'badValue', 'evenWorse' and 'OHNOEZ'
//
process.argv.splice(3, 3);
nconf.system.loadArgv();
nconf.sources['argv'].loadArgv();
process.stdout.write(nconf.get('something'));

View file

@ -5,7 +5,6 @@
*
*/
var nconf = require('../../../lib/nconf');
var nconf = require('../../../lib/nconf').env();
nconf.env = true;
process.stdout.write(nconf.get('SOMETHING'));

View file

@ -7,6 +7,6 @@
var nconf = require('../../../lib/nconf');
var provider = new (nconf.Provider)({ argv: true });
var provider = new (nconf.Provider)().argv();
process.stdout.write(provider.get('something'));

View file

@ -7,6 +7,6 @@
var nconf = require('../../../lib/nconf');
var provider = new (nconf.Provider)({ env: true });
var provider = new (nconf.Provider)().env();
process.stdout.write(provider.get('SOMETHING'));

View file

@ -43,8 +43,8 @@ exports.assertSystemConf = function (options) {
});
}
spawn(process.argv[0], [options.script].concat(options.argv), { env: env })
.stdout.once('data', this.callback.bind(this, null));
var child = spawn('node', [options.script].concat(options.argv), { env: env });
child.stdout.once('data', this.callback.bind(this, null));
},
"should respond with the value passed into the script": function (_, data) {
assert.equal(data.toString(), 'foobar');

View file

@ -28,7 +28,7 @@ vows.describe('nconf').addBatch({
"the use() method": {
"should instaniate the correct store": function () {
nconf.use('memory');
assert.instanceOf(nconf.memory, nconf.stores.Memory);
assert.instanceOf(nconf.sources['memory'], nconf.stores.Memory);
}
},
"it should": {

View file

@ -24,13 +24,13 @@ vows.describe('nconf/provider').addBatch({
"calling the use() method with the same store type and different options": {
topic: new nconf.Provider().use('file', { file: files[0] }),
"should use a new instance of the store type": function (provider) {
var old = provider.file;
var old = provider.sources['file'];
assert.equal(provider.file.file, files[0]);
assert.equal(provider.sources.file.file, files[0]);
provider.use('file', { file: files[1] });
assert.notStrictEqual(old, provider.file);
assert.equal(provider.file.file, files[1]);
assert.notStrictEqual(old, provider.sources.file);
assert.equal(provider.sources.file.file, files[1]);
}
},
"when 'argv' is true": helpers.assertSystemConf({
@ -40,7 +40,7 @@ vows.describe('nconf/provider').addBatch({
"when 'env' is true": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'provider-env.js'),
env: { SOMETHING: 'foobar' }
}),
})
},
"the default nconf provider": {
"when 'argv' is set to true": helpers.assertSystemConf({
@ -66,27 +66,9 @@ vows.describe('nconf/provider').addBatch({
"should have the result merged in": function (provider) {
provider.load();
provider.merge(override);
helpers.assertMerged(null, provider.file.store);
}
},
"when sources are passed in": {
topic: new nconf.Provider({
sources: {
user: {
type: 'file',
file: files[0]
},
global: {
type: 'file',
file: files[1]
}
}
}),
"should have the result merged in": function (provider) {
helpers.assertMerged(null, provider.load());
helpers.assertMerged(null, provider.sources.file.store);
}
}
}
}
}).export(module);

22
test/stores/argv-test.js Normal file
View file

@ -0,0 +1,22 @@
/*
* argv-test.js: Tests for the nconf argv store.
*
* (C) 2011, Charlie Robbins
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/argv').addBatch({
"An instance of nconf.stores.Argv": {
topic: new nconf.stores.Argv(),
"should have the correct methods defined": function (argv) {
assert.isFunction(argv.loadSync);
assert.isFunction(argv.loadArgv);
assert.isFalse(argv.options);
}
}
}).export(module);

23
test/stores/env-test.js Normal file
View file

@ -0,0 +1,23 @@
/*
* env-test.js: Tests for the nconf env store.
*
* (C) 2011, Charlie Robbins
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/env').addBatch({
"An instance of nconf.stores.Env": {
topic: new nconf.stores.Env(),
"should have the correct methods defined": function (env) {
assert.isFunction(env.loadSync);
assert.isFunction(env.loadEnv);
assert.isArray(env.options);
assert.lengthOf(env.options, 0);
}
}
}).export(module);

View file

@ -0,0 +1,30 @@
/*
* literal-test.js: Tests for the nconf literal store.
*
* (C) 2011, Charlie Robbins
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/literal').addBatch({
"An instance of nconf.stores.Literal": {
topic: new nconf.stores.Literal({
foo: 'bar',
one: 2
}),
"should have the correct methods defined": function (literal) {
assert.equal(literal.type, 'literal');
assert.isFunction(literal.get);
assert.isFunction(literal.set);
assert.isFunction(literal.merge);
},
"should have the correct values in the store": function (literal) {
assert.equal(literal.store.foo, 'bar');
assert.equal(literal.store.one, 2);
}
}
}).export(module);

View file

@ -1,26 +0,0 @@
/*
* system-test.js: Tests for the nconf system store.
*
* (C) 2011, Charlie Robbins
*
*/
var vows = require('vows'),
assert = require('assert'),
helpers = require('../helpers'),
nconf = require('../../lib/nconf');
vows.describe('nconf/stores/system').addBatch({
"An instance of nconf.stores.System": {
topic: new nconf.stores.System(),
"should have the correct methods defined": function (system) {
assert.isFunction(system.loadSync);
assert.isFunction(system.loadOverrides);
assert.isFunction(system.loadArgv);
assert.isFunction(system.loadEnv);
assert.isFalse(system.argv);
assert.isFalse(system.env);
assert.isNull(system.overrides);
}
}
}).export(module);