[api test] Updated test/provider-test.js and associated merge implementation
This commit is contained in:
parent
e8904e95c6
commit
fb392ddc51
10 changed files with 245 additions and 80 deletions
39
lib/nconf.js
39
lib/nconf.js
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
async = require('async'),
|
async = require('async'),
|
||||||
|
common = require('./nconf/common'),
|
||||||
Provider = require('./nconf/provider').Provider,
|
Provider = require('./nconf/provider').Provider,
|
||||||
nconf = module.exports = Object.create(Provider.prototype);
|
nconf = module.exports = Object.create(Provider.prototype);
|
||||||
|
|
||||||
|
@ -37,40 +38,10 @@ nconf.key = function () {
|
||||||
return Array.prototype.slice.call(arguments).join(':');
|
return Array.prototype.slice.call(arguments).join(':');
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
// ### function loadFiles (files)
|
|
||||||
// #### @files {Array} List of files to load.
|
|
||||||
// Loads all the data in the specified `files`.
|
|
||||||
//
|
|
||||||
nconf.loadFiles = function (files, callback) {
|
|
||||||
if (!files) {
|
|
||||||
return callback(null, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
var allData = {};
|
|
||||||
|
|
||||||
function loadFile (file, next) {
|
|
||||||
fs.readFile(file, function (err, data) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = JSON.parse(data.toString());
|
|
||||||
Object.keys(data).forEach(function (key) {
|
|
||||||
allData[key] = data[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async.forEach(files, loadFile, function (err) {
|
|
||||||
return err ? callback(err) : callback(null, allData);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Expose the various components included with nconf
|
// Expose the various components included with nconf
|
||||||
//
|
//
|
||||||
nconf.stores = require('./nconf/stores');
|
nconf.loadFiles = common.loadFiles;
|
||||||
nconf.Provider = Provider;
|
nconf.formats = require('./nconf/formats');
|
||||||
|
nconf.stores = require('./nconf/stores');
|
||||||
|
nconf.Provider = Provider;
|
53
lib/nconf/common.js
Normal file
53
lib/nconf/common.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* utils.js: Utility functions for the nconf module.
|
||||||
|
*
|
||||||
|
* (C) 2011, Charlie Robbins
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
async = require('async'),
|
||||||
|
formats = require('./formats'),
|
||||||
|
stores = require('./stores');
|
||||||
|
|
||||||
|
var common = exports;
|
||||||
|
|
||||||
|
//
|
||||||
|
// ### function loadFiles (files)
|
||||||
|
// #### @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`.
|
||||||
|
//
|
||||||
|
common.loadFiles = function (files, callback) {
|
||||||
|
if (!files) {
|
||||||
|
return callback(null, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = Array.isArray(files) ? { files: files } : files,
|
||||||
|
store = new stores.Memory();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set the default JSON format if not already
|
||||||
|
// specified
|
||||||
|
//
|
||||||
|
options.format = options.format || formats.json;
|
||||||
|
|
||||||
|
function loadFile (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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async.forEach(files, loadFile, function (err) {
|
||||||
|
return err ? callback(err) : callback(null, store.store);
|
||||||
|
});
|
||||||
|
};
|
28
lib/nconf/formats.js
Normal file
28
lib/nconf/formats.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* formats.js: Default formats supported by nconf
|
||||||
|
*
|
||||||
|
* (C) 2011, Charlie Robbins
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
var ini = require('ini');
|
||||||
|
|
||||||
|
var formats = exports;
|
||||||
|
|
||||||
|
//
|
||||||
|
// ### @json
|
||||||
|
// Standard JSON format which pretty prints `.stringify()`.
|
||||||
|
//
|
||||||
|
formats.json = {
|
||||||
|
stringify: function (obj) {
|
||||||
|
return JSON.stringify(obj, null, 2)
|
||||||
|
},
|
||||||
|
parse: JSON.parse
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// ### @ini
|
||||||
|
// Standard INI format supplied from the `ini` module
|
||||||
|
// http://en.wikipedia.org/wiki/INI_file
|
||||||
|
//
|
||||||
|
formats.ini = ini;
|
|
@ -5,7 +5,9 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var optimist = require('optimist'),
|
var async = require('async'),
|
||||||
|
optimist = require('optimist'),
|
||||||
|
common = require('./common'),
|
||||||
stores = require('./stores');
|
stores = require('./stores');
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -62,7 +64,7 @@ Provider.prototype.get = function (key, callback) {
|
||||||
return this.overrides[key];
|
return this.overrides[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.store.get(key, callback);
|
return this._execute('get', 1, key, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -73,19 +75,56 @@ Provider.prototype.get = function (key, callback) {
|
||||||
// Sets the `value` for the specified `key` in this instance.
|
// Sets the `value` for the specified `key` in this instance.
|
||||||
//
|
//
|
||||||
Provider.prototype.set = function (key, value, callback) {
|
Provider.prototype.set = function (key, value, callback) {
|
||||||
return this.store.set(key, value, callback);
|
return this._execute('set', 2, key, value, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// ### function merge (key, value)
|
// ### function merge ([key,] value [, callback])
|
||||||
// #### @key {string} Key to merge the value into
|
// #### @key {string} Key to merge the value into
|
||||||
// #### @value {literal|Object} Value to merge into the key
|
// #### @value {literal|Object} Value to merge into the key
|
||||||
// Merges the properties in `value` into the existing object value
|
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
||||||
// at `key`. If the existing value `key` is not an Object, it will be
|
// Merges the properties in `value` into the existing object value at `key`.
|
||||||
// completely overwritten.
|
|
||||||
//
|
//
|
||||||
Provider.prototype.merge = function (key, value, callback) {
|
// 1. If the existing value `key` is not an Object, it will be completely overwritten.
|
||||||
return this.store.merge(key, value, callback);
|
// 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// ### 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);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -95,7 +134,7 @@ Provider.prototype.merge = function (key, value, callback) {
|
||||||
// Removes the value for the specified `key` from this instance.
|
// Removes the value for the specified `key` from this instance.
|
||||||
//
|
//
|
||||||
Provider.prototype.clear = function (key, callback) {
|
Provider.prototype.clear = function (key, callback) {
|
||||||
return this.store.clear(key, callback);
|
return this._execute('clear', 1, key, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -113,16 +152,9 @@ Provider.prototype.load = function (callback) {
|
||||||
return this.store.loadSync();
|
return this.store.loadSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.store.load) {
|
return !this.store.load
|
||||||
var error = new Error('nconf store ' + this.store.type + ' has no load() method');
|
? onError(new Error('nconf store ' + this.store.type + ' has no load() method'), callback)
|
||||||
if (callback) {
|
: this.store.load(callback);
|
||||||
return callback (error);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.store.load(callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -147,16 +179,9 @@ Provider.prototype.save = function (value, callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.store.save) {
|
return !this.store.save
|
||||||
var error = new Error('nconf store ' + this.store.type + ' has no save() method');
|
? onError(new Error('nconf store ' + this.store.type + ' has no save() method'), callback)
|
||||||
if (callback) {
|
: this.store.save(value, callback);
|
||||||
return callback (error);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.store.save(value, callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -165,9 +190,36 @@ Provider.prototype.save = function (value, callback) {
|
||||||
// Clears all keys associated with this instance.
|
// Clears all keys associated with this instance.
|
||||||
//
|
//
|
||||||
Provider.prototype.reset = function (callback) {
|
Provider.prototype.reset = function (callback) {
|
||||||
return this.store.reset(callback);
|
return this._execute('reset', 0, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// ### @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 `this.store`, 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,
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// ### getter @useArgv {boolean}
|
// ### getter @useArgv {boolean}
|
||||||
// Gets a property indicating if we should wrap calls to `.get`
|
// Gets a property indicating if we should wrap calls to `.get`
|
||||||
|
@ -185,4 +237,15 @@ Provider.prototype.__defineGetter__('useArgv', function () {
|
||||||
Provider.prototype.__defineSetter__('useArgv', function (val) {
|
Provider.prototype.__defineSetter__('useArgv', function (val) {
|
||||||
this._useArgv = val || false;
|
this._useArgv = val || false;
|
||||||
this.overrides = this.overrides || optimist.argv;
|
this.overrides = this.overrides || optimist.argv;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Throw the `err` if a callback is not supplied
|
||||||
|
//
|
||||||
|
function onError(err, callback) {
|
||||||
|
if (callback) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
|
@ -5,9 +5,10 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
util = require('util'),
|
util = require('util'),
|
||||||
|
formats = require('../formats'),
|
||||||
Memory = require('./memory').Memory;
|
Memory = require('./memory').Memory;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -26,12 +27,7 @@ var File = exports.File = function (options) {
|
||||||
this.type = 'file';
|
this.type = 'file';
|
||||||
this.file = options.file;
|
this.file = options.file;
|
||||||
this.dir = options.dir || process.cwd();
|
this.dir = options.dir || process.cwd();
|
||||||
this.format = options.format || {
|
this.format = options.format || formats.json;
|
||||||
stringify: function (obj) {
|
|
||||||
return JSON.stringify(obj, null, 2)
|
|
||||||
},
|
|
||||||
parse: JSON.parse
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inherit from the Memory store
|
// Inherit from the Memory store
|
||||||
|
|
|
@ -156,7 +156,7 @@ Memory.prototype.merge = function (key, value) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the current value at the key target is not an `Object`,
|
// If the current value at the key target is not an `Object`,
|
||||||
// of is an `Array` then simply override it because the new value
|
// or is an `Array` then simply override it because the new value
|
||||||
// is an Object.
|
// is an Object.
|
||||||
//
|
//
|
||||||
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
|
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
|
||||||
|
@ -177,4 +177,4 @@ Memory.prototype.reset = function () {
|
||||||
this.mtimes = {};
|
this.mtimes = {};
|
||||||
this.store = {};
|
this.store = {};
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
|
@ -10,6 +10,7 @@
|
||||||
"keywords": ["configuration", "key value store", "plugabble"],
|
"keywords": ["configuration", "key value store", "plugabble"],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "0.1.x",
|
"async": "0.1.x",
|
||||||
|
"ini": "1.x.x",
|
||||||
"optimist": "0.2.x",
|
"optimist": "0.2.x",
|
||||||
"pkginfo": "0.2.x"
|
"pkginfo": "0.2.x"
|
||||||
},
|
},
|
||||||
|
|
8
test/fixtures/merge/file1.json
vendored
Normal file
8
test/fixtures/merge/file1.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"apples": true,
|
||||||
|
"bananas": true,
|
||||||
|
"candy": {
|
||||||
|
"something1": true,
|
||||||
|
"something2": true
|
||||||
|
}
|
||||||
|
}
|
8
test/fixtures/merge/file2.json
vendored
Normal file
8
test/fixtures/merge/file2.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"candy": {
|
||||||
|
"something3": true,
|
||||||
|
"something4": true
|
||||||
|
},
|
||||||
|
"dates": true,
|
||||||
|
"elderberries": true
|
||||||
|
}
|
|
@ -10,9 +10,12 @@ var assert = require('assert'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
spawn = require('child_process').spawn,
|
spawn = require('child_process').spawn,
|
||||||
vows = require('vows'),
|
vows = require('vows'),
|
||||||
nconf = require('../lib/nconf')
|
nconf = require('../lib/nconf');
|
||||||
|
|
||||||
var first = '/path/to/file1',
|
var mergeFixtures = path.join(__dirname, 'fixtures', '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';
|
second = '/path/to/file2';
|
||||||
|
|
||||||
function assertDefaults (script) {
|
function assertDefaults (script) {
|
||||||
|
@ -27,6 +30,18 @@ function assertDefaults (script) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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({
|
vows.describe('nconf/provider').addBatch({
|
||||||
"When using nconf": {
|
"When using nconf": {
|
||||||
"an instance of 'nconf.Provider'": {
|
"an instance of 'nconf.Provider'": {
|
||||||
|
@ -48,4 +63,26 @@ vows.describe('nconf/provider').addBatch({
|
||||||
"when 'useArgv' is true": assertDefaults(path.join(__dirname, 'fixtures', 'scripts', 'default-override.js'))
|
"when 'useArgv' is true": assertDefaults(path.join(__dirname, 'fixtures', 'scripts', 'default-override.js'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}).addBatch({
|
||||||
|
"When using nconf": {
|
||||||
|
"an instance of 'nconf.Provider'": {
|
||||||
|
"the merge() method": {
|
||||||
|
topic: new nconf.Provider().use('file', { file: files[1] }),
|
||||||
|
"should have the result merged in": function (provider) {
|
||||||
|
provider.load();
|
||||||
|
provider.merge(override);
|
||||||
|
assertMerged(provider);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"the mergeFiles() method": {
|
||||||
|
topic: function () {
|
||||||
|
var provider = new nconf.Provider().use('memory');
|
||||||
|
provider.mergeFiles(files, this.callback.bind(this, null, provider))
|
||||||
|
},
|
||||||
|
"should have the result merged in": function (_, provider) {
|
||||||
|
assertMerged(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}).export(module);
|
}).export(module);
|
Loading…
Reference in a new issue