diff --git a/README.md b/README.md index 0991b23..aaf63f1 100644 --- a/README.md +++ b/README.md @@ -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 // @@ -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'); ``` diff --git a/lib/nconf/common.js b/lib/nconf/common.js index 4e7ba7b..3096fab 100644 --- a/lib/nconf/common.js +++ b/lib/nconf/common.js @@ -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; +}; diff --git a/lib/nconf/stores/argv.js b/lib/nconf/stores/argv.js index 666c853..44ae4ce 100644 --- a/lib/nconf/stores/argv.js +++ b/lib/nconf/stores/argv.js @@ -6,6 +6,7 @@ */ var util = require('util'), + common = require('../common'), Memory = require('./memory').Memory; // @@ -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 @@ -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); } }); diff --git a/lib/nconf/stores/env.js b/lib/nconf/stores/env.js index 0b33db6..e429fe8 100644 --- a/lib/nconf/stores/env.js +++ b/lib/nconf/stores/env.js @@ -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) { diff --git a/lib/nconf/stores/memory.js b/lib/nconf/stores/memory.js index 60afc01..db71387 100644 --- a/lib/nconf/stores/memory.js +++ b/lib/nconf/stores/memory.js @@ -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); @@ -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; }; diff --git a/test/complete-test.js b/test/complete-test.js index c2df651..88f0374 100644 --- a/test/complete-test.js +++ b/test/complete-test.js @@ -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 () { @@ -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); diff --git a/test/provider-test.js b/test/provider-test.js index ac4c5fd..e9cd40a 100644 --- a/test/provider-test.js +++ b/test/provider-test.js @@ -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({ diff --git a/test/stores/argv-test.js b/test/stores/argv-test.js index 85bedab..a6f4bff 100644 --- a/test/stores/argv-test.js +++ b/test/stores/argv-test.js @@ -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); \ No newline at end of file