Skip to content

Commit

Permalink
[api test] Updated test/provider-test.js and associated merge impleme…
Browse files Browse the repository at this point in the history
…ntation
  • Loading branch information
indexzero committed Aug 28, 2011
1 parent e8904e9 commit fb392dd
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 80 deletions.
39 changes: 5 additions & 34 deletions lib/nconf.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

var fs = require('fs'),
async = require('async'),
common = require('./nconf/common'),
Provider = require('./nconf/provider').Provider,
nconf = module.exports = Object.create(Provider.prototype);

Expand Down Expand Up @@ -37,40 +38,10 @@ nconf.key = function () {
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
//
nconf.stores = require('./nconf/stores');
nconf.Provider = Provider;
nconf.loadFiles = common.loadFiles;
nconf.formats = require('./nconf/formats');
nconf.stores = require('./nconf/stores');
nconf.Provider = Provider;
53 changes: 53 additions & 0 deletions lib/nconf/common.js
Original file line number Diff line number Diff line change
@@ -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 changes: 28 additions & 0 deletions lib/nconf/formats.js
Original file line number Diff line number Diff line change
@@ -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;
127 changes: 95 additions & 32 deletions lib/nconf/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
*
*/

var optimist = require('optimist'),
var async = require('async'),
optimist = require('optimist'),
common = require('./common'),
stores = require('./stores');

//
Expand Down Expand Up @@ -62,7 +64,7 @@ Provider.prototype.get = function (key, callback) {
return this.overrides[key];
}

return this.store.get(key, callback);
return this._execute('get', 1, key, callback);
};

//
Expand All @@ -73,19 +75,56 @@ 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.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
// #### @value {literal|Object} Value to merge into the key
// Merges the properties in `value` into the existing object value
// at `key`. If the existing value `key` is not an Object, it will be
// completely overwritten.
// #### @callback {function} **Optional** Continuation to respond to when complete.
// Merges the properties in `value` into the existing object value at `key`.
//
// 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);
};

//
// ### 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.merge = function (key, value, callback) {
return this.store.merge(key, value, callback);
Provider.prototype.mergeFiles = function (files, callback) {
var self = this;

common.loadFiles(files, function (err, merged) {
return !err
? self.merge(merged, callback)
: onError(err);
});
};

//
Expand All @@ -95,7 +134,7 @@ Provider.prototype.merge = function (key, value, callback) {
// Removes the value for the specified `key` from this instance.
//
Provider.prototype.clear = function (key, callback) {
return this.store.clear(key, callback);
return this._execute('clear', 1, key, callback);
};

//
Expand All @@ -113,16 +152,9 @@ Provider.prototype.load = function (callback) {
return this.store.loadSync();
}

if (!this.store.load) {
var error = new Error('nconf store ' + this.store.type + ' has no load() method');
if (callback) {
return callback (error);
}

throw error;
}

return this.store.load(callback);
return !this.store.load
? onError(new Error('nconf store ' + this.store.type + ' has no load() method'), callback)
: this.store.load(callback);
};

//
Expand All @@ -147,16 +179,9 @@ Provider.prototype.save = function (value, callback) {
}
}

if (!this.store.save) {
var error = new Error('nconf store ' + this.store.type + ' has no save() method');
if (callback) {
return callback (error);
}

throw error;
}

return this.store.save(value, callback);
return !this.store.save
? onError(new Error('nconf store ' + this.store.type + ' has no save() method'), callback)
: this.store.save(value, callback);
};

//
Expand All @@ -165,9 +190,36 @@ Provider.prototype.save = function (value, callback) {
// Clears all keys associated with this instance.
//
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}
// Gets a property indicating if we should wrap calls to `.get`
Expand All @@ -185,4 +237,15 @@ Provider.prototype.__defineGetter__('useArgv', function () {
Provider.prototype.__defineSetter__('useArgv', function (val) {
this._useArgv = val || false;
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;
}
14 changes: 5 additions & 9 deletions lib/nconf/stores/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
*
*/

var fs = require('fs'),
path = require('path'),
util = require('util'),
var fs = require('fs'),
path = require('path'),
util = require('util'),
formats = require('../formats'),
Memory = require('./memory').Memory;

//
Expand All @@ -26,12 +27,7 @@ var File = exports.File = function (options) {
this.type = 'file';
this.file = options.file;
this.dir = options.dir || process.cwd();
this.format = options.format || {
stringify: function (obj) {
return JSON.stringify(obj, null, 2)
},
parse: JSON.parse
};
this.format = options.format || formats.json;
};

// Inherit from the Memory store
Expand Down
4 changes: 2 additions & 2 deletions lib/nconf/stores/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Memory.prototype.merge = function (key, value) {

//
// 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.
//
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
Expand All @@ -177,4 +177,4 @@ Memory.prototype.reset = function () {
this.mtimes = {};
this.store = {};
return true;
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"keywords": ["configuration", "key value store", "plugabble"],
"dependencies": {
"async": "0.1.x",
"ini": "1.x.x",
"optimist": "0.2.x",
"pkginfo": "0.2.x"
},
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/merge/file1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"apples": true,
"bananas": true,
"candy": {
"something1": true,
"something2": true
}
}
Loading

0 comments on commit fb392dd

Please sign in to comment.