Skip to content

Commit

Permalink
Support parsing simple values from env/argv strings (#273)
Browse files Browse the repository at this point in the history
* simple parse, indexzero/nconf#72

* documentation for tryParse option

* Combine JSON parsing and simple parsing
  • Loading branch information
strongdevlancer committed Oct 21, 2017
1 parent 81b32d9 commit 2daadfd
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 30 deletions.
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -191,6 +191,9 @@ Responsible for loading the values parsed from `process.argv` by `yargs` into th
### Env
Responsible for loading the values parsed from `process.env` into the configuration hierarchy.
Usually the env variables values are loaded into the configuration as strings.
To ensure well-known strings ('false', 'true', 'null', 'undefined', '3', '5.1') and JSON values
are properly parsed, the `parseValues` boolean option is available.
``` js
//
Expand Down Expand Up @@ -225,7 +228,8 @@ Responsible for loading the values parsed from `process.env` into the configurat
separator: '__',
match: /^whatever_matches_this_will_be_whitelisted/
whitelist: ['database__host', 'only', 'load', 'these', 'values', 'if', 'whatever_doesnt_match_but_is_whitelisted_gets_loaded_too'],
lowerCase: true
lowerCase: true,
parseValues: true
});
var dbHost = nconf.get('database:host');
```
Expand Down
20 changes: 20 additions & 0 deletions lib/nconf/common.js
Expand Up @@ -121,3 +121,23 @@ common.merge = function (objs) {
common.capitalize = function (str) {
return str && str[0].toUpperCase() + str.slice(1);
};

//
// ### function parseValues (any)
// #### @any {string} String to parse as native data-type or return as is
// try to parse `any` as a native data-type
//
common.parseValues = function (value) {
var val = value;

try {
val = JSON.parse(value);
} catch (ignore) {
// Check for any other well-known strings that should be "parsed"
if (value === 'undefined'){
val = void 0;
}
}

return val;
};
15 changes: 12 additions & 3 deletions lib/nconf/stores/argv.js
Expand Up @@ -6,6 +6,7 @@
*/

var util = require('util'),
common = require('../common'),
Memory = require('./memory').Memory;

//
Expand All @@ -17,10 +18,12 @@ var util = require('util'),
var Argv = exports.Argv = function (options, usage) {
Memory.call(this, options);

options = options || {};
this.type = 'argv';
this.readOnly = true;
this.options = options || false;
this.options = options;
this.usage = usage;
this.parseValues = options.parseValues || false;
};

// Inherit from the Memory store
Expand Down Expand Up @@ -58,8 +61,14 @@ Argv.prototype.loadArgv = function () {

this.readOnly = false;
Object.keys(argv).forEach(function (key) {
if (typeof argv[key] !== 'undefined') {
self.set(key, argv[key]);
var val = argv[key];

if (typeof val !== 'undefined') {
if (self.parseValues) {
val = common.parseValues(val);
}

self.set(key, val);
}
});

Expand Down
15 changes: 1 addition & 14 deletions lib/nconf/stores/env.js
Expand Up @@ -83,20 +83,7 @@ Env.prototype.loadEnv = function () {
var val = env[key];

if (self.parseJson) {
try {
var ret = JSON.parse(val);

// apply JSON parsing only if its non-primitive types: JSON Object or Array
// avoid breaking backward-compatibility
if (typeof ret !== 'number' &&
typeof ret !== 'string' &&
typeof ret !== 'boolean') {
val = ret;
}

} catch (ignore) {
//ignore
}
val = common.parseValues(val);
}

if (self.separator) {
Expand Down
4 changes: 4 additions & 0 deletions lib/nconf/stores/memory.js
Expand Up @@ -23,6 +23,7 @@ var Memory = exports.Memory = function (options) {
this.readOnly = false;
this.loadFrom = options.loadFrom || null;
this.logicalSeparator = options.logicalSeparator || ':';
this.parseValues = options.parseValues || false;

if (this.loadFrom) {
this.store = common.loadFilesSync(this.loadFrom);
Expand Down Expand Up @@ -100,6 +101,9 @@ Memory.prototype.set = function (key, value) {

// Set the specified value in the nested JSON structure
key = path.shift();
if (this.parseValues) {
value = common.parseValues.call(common, value);
}
target[key] = value;
return true;
};
Expand Down
12 changes: 2 additions & 10 deletions test/complete-test.js
Expand Up @@ -161,7 +161,7 @@ vows.describe('nconf/multiple-stores').addBatch({
}).addBatch({
// Threw this in it's own batch to make sure it's run separately from the
// sync check
"When using env with parseJson:true": {
"When using env with parseValues:true": {
topic: function () {
var that = this;
helpers.cp(complete, completeTest, function () {
Expand All @@ -175,15 +175,7 @@ vows.describe('nconf/multiple-stores').addBatch({
var val = process.env[key];

try {
var ret = JSON.parse(val);

// apply JSON parsing only if its non-primitive types: JSON Object or Array
// avoid breaking backward-compatibility
if (typeof ret !== 'number' &&
typeof ret !== 'string' &&
typeof ret !== 'boolean') {
val = ret;
}
val = JSON.parse(val);
} catch (err) {}

assert.deepEqual(nconf.get(key), val);
Expand Down
37 changes: 36 additions & 1 deletion test/provider-test.js
Expand Up @@ -47,7 +47,42 @@ vows.describe('nconf/provider').addBatch({
"when 'env' is true": helpers.assertSystemConf({
script: path.join(fixturesDir, 'scripts', 'provider-env.js'),
env: { SOMETHING: 'foobar' }
})
}),
"when 'env' is true and 'parseValues' option is true": {
topic: function() {
var env = {
SOMETHING: 'foobar',
SOMEBOOL: 'true',
SOMENULL: 'null',
SOMEUNDEF: 'undefined',
SOMEINT: '3600',
SOMEFLOAT: '0.5',
SOMEBAD: '5.1a'
};
var oenv = {};
Object.keys(env).forEach(function (key) {
if (process.env[key]) oenv[key] = process.env[key];
process.env[key] = env[key];
});
var provider = new nconf.Provider().use('env', {parseValues: true});
Object.keys(env).forEach(function (key) {
delete process.env[key];
if (oenv[key]) process.env[key] = oenv[key];
});
return provider;
},
"should respond with parsed values": function (provider) {

assert.equal(provider.get('SOMETHING'), 'foobar');
assert.strictEqual(provider.get('SOMEBOOL'), true);
assert.notEqual(provider.get('SOMEBOOL'), 'true');
assert.strictEqual(provider.get('SOMENULL'), null);
assert.strictEqual(provider.get('SOMEUNDEF'), undefined);
assert.strictEqual(provider.get('SOMEINT'), 3600);
assert.strictEqual(provider.get('SOMEFLOAT'), .5);
assert.strictEqual(provider.get('SOMEBAD'), '5.1a');
}
}
},
"the default nconf provider": {
"when 'argv' is set to true": helpers.assertSystemConf({
Expand Down
2 changes: 1 addition & 1 deletion test/stores/argv-test.js
Expand Up @@ -16,7 +16,7 @@ vows.describe('nconf/stores/argv').addBatch({
"should have the correct methods defined": function (argv) {
assert.isFunction(argv.loadSync);
assert.isFunction(argv.loadArgv);
assert.isFalse(argv.options);
assert.deepEqual(argv.options, {});
}
}
}).export(module);

0 comments on commit 2daadfd

Please sign in to comment.