'use strict';
var path = require('path');
var loader = require('./loader');
var cli = require('./cli');
var merge = require('lodash/object/merge');
var defaults = require('lodash/object/defaults');
var template = require('lodash/string/template');
var every = require('lodash/collection/every');
var each = require('lodash/collection/each');
var isObject = require('lodash/lang/isObject');
var isNull = require('lodash/lang/isNull');
var isUndefined = require('lodash/lang/isUndefined');
/*eslint-disable */
/**
* @name Config
* @constructor
* @description
* A utility which helps you managing to manage and organize your configuration for different environment nicely spread out over multiple files.
*
* The configuration can be broken down as follows:
* (By default the config utility looks for a config folder at the root of your project)
*
* ```
* config/
* ├── default.js
* ├── default
* │ ├── app.js
* │ ├── breakpoint.js
* │ ├── credentials.js
* │ ├── fear.js
* │ ├── karma.js
* │ ├── mustache.js
* │ ├── packages.js
* │ ├── paths.js
* │ ├── protractor.js
* │ └── webdriverio.js
* ├── development
* │ ├── app.js
* │ ├── karma.js
* │ ├── protractor.js
* │ ├── webdriverio.js
* │ └── webserver.js
* └── integrated
* ├── app.js
* ├── karma.js
* ├── protractor.js
* ├── webdriverio.js
* └── webserver.js
* ```
* # Introduction
* In the default.js file you can require your default configuration values which resides in the default fodler. If no default.js file is present, the config utility will try to load
* all the default properties from the files in the folder. Using the filename as key on the object.
*
* Default the config utility will fallback to the devlopment environment, but this can be overriden be providing --env args to a commandline utility or by
* setting the NODE_ENV variable to a different environment.
*
* ## Getting a value
* A configuration value can be requested by providing the correct key to the get function:
*
* ```js
* var karma = config.get('karma');
* ```
*
* The above function will return the full configuration object provided by the default/karma.js file overriden by (development/integrated)/karma.js.
*
* It's also possible to get a specific config value from the karma file.
* If the karma.js file contains the following code:
*
* ```js
* module.exports = {
* server : '127.0.0.1'
* }
* ```
*
* Calling the get function with the following keys will return the coresponding value in the karma.js file:
*
* ```js
* var server = config.get('karma.server');
* ```
*
* ## Templating the return value
* It's possible to template a key using the handlebars syntax.
*
* If for example a certain path.js config file would look as follows:
*
* ```js
* module.exports = {
* css: '{{base}}/css',
* js: '{{base}}/javascript'
* }
* ```
*
* Using the get syntax with a certain context object will return the templated string
*
* ```js
* var css = config.get('paths.css',{ base: 'app' }); // returns: 'app/css'
* var paths = config.get('paths') // returns: { css: 'app/css', js: 'app/javascript' }
* ```
*
* ## Changing the target
* It is also possible to provide a target string as second or third parameter. This will switch the target folder where configuration files get loaded from:
*
* Given the following config folder structure:
*
* ```
* ├── buser
* ├── default
* │ └── config.js
* │ ├── cardTypes.js
* │ ├── checkoutHeader.js
* │ ├── config.js
* │ ├── content.js
* │ ├── countries.js
* │ ├── espots.js
* │ ├── featureSwitch.js
* │ ├── globalHeader.js
* │ ├── instructions.js
* │ ├── labels.js
* │ ├── miscellaneous.js
* │ ├── pageContent.js
* │ ├── userLogon.js
* │ └── userRegistration.js
* ├── defaults.js
* ├── mobileapp
* │ └── config.js
* └── tsop
* ├── checkoutHeader.js
* ├── config.js
* └── labels.js
* ```
* You can provide a different target via:
*
* ```
* var mobile = config.get('config', 'mobile') //returns: config object in mobile folder
* var tsop = config.get('config', { base : 'tmp' }, 'tsop) //returns: config object in mobile folder while templating the result
* ```
*
* ## Options
* The config utility can be configured using a configurations object. The default values are as follows:
*
* ```json
* {
* "root": "config", //Root folder where the utility will look for configuration files
* "delimeters": /{{([\s\S]+?)}}/g, //Regex used as matching algorith for templating values
* "target": "development", //The default target folder
* "debug": false //Determines if the utility logs verbose information
* }
* ```
*
* These defaults can be cahnged as follows:
*
* ```js
* var config = require('fear-core').config({
* root: 'mocks/channel', //changes the root folder
* delimeters: /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g // Changes templating algorithm to '${value}'
* })
* ```
*
* ## Debugging
* It is possible to let the config utility log debug statements by setting the NODE_DEBUG environment variable to config.
*
* ```
* NODE_DEBUG=config gulp ...
* ```
*
* Or by setting debug to true in the options object.
*
* ```js
* var config = require('fear-core').config({ debug: true });
* ```
*
* @param {object} fsLoader
* @param {object} cli
* @param {object} options
* @param {string} options.root The root config folder, defaults to config
* @param {regex} options.delimeters Regex used for the templating algorithm, defaults to handlebars matching
*
* @throws Will throw an error when there is no default.js or default folder in the root folder
*/
/*eslint-enable */
function Config(fs, cli, opt) {
var options = {
root: 'config',
delimeters: /{{([\s\S]+?)}}/g,
target: 'development',
debug: false
};
if(this.debug) {
cli.env.NODE_DEBUG = 'config';
}
// Debug logging function for the config object. Only logs when NODE_DEBUG contains config
this._debug = cli.debugLog('config');
this._debug('Starting config object creation.');
// Make sure that options are overridable use local options variable as default
this._options = defaults(opt || {}, options);
this._fs = fs;
// Set the current environment taking in to account the given arguments and current node environment
// always fall back to 'development'
this._env = cli.argv.env || cli.env.NODE_ENV || options.target;
this._target = this._env;
this._debug('Using the following root: ' + this._options.root);
this._debug('Current target set to: ' + this._target);
// Load default configuration either via a default.js file in the root folder
// Otherwise fallback to loading all the files in the default folder and generate an object from there
this._defaultMap = fs.load(path.join(this._options.root, 'default'), process.cwd());
if(this._defaultMap) {
this._debug('Default configuration loaded from default.js file.');
}else {
this._defaultMap = fs.loadDir(path.join(this._options.root, 'default'), process.cwd());
this._debug('Default configuration constructed from files in the default folder');
}
if(this._defaultMap === null || this._defaultMap === undefined) {
this._debug('No default.js or default folder found');
throw new Error('Default config file or default folder is not present!');
}
this._devMap = fs.load(path.join(this._options.root, this.env(), 'development')) || {};
}
Config.prototype = {
/**
* @name config.get
* @public
* @description
* Returns the configuration value for the requested key, the return value can be templated given a certain context object.
*
* @param {string} properties String of a key value in the configuration, nested configuration can be reached by providing the keys seperated by dots.
* @param {object|string} [context]
* @param {string} [target]
*
* @throws When no parameters are provided
* @throws When properties parameter is not a string
*
* @returns {string|object} Config value of the requested property
*/
get: function (properties, context, target) {
var result = this._configMap,
rootKey = '',
config = this,
temp,
configHash;
if(arguments.length === 0) {
throw new Error('No arguments provided');
}
if(typeof properties !== 'string') {
throw new Error('Properties parameter should be a string.');
}
//When context is a string swap it with the target
if(typeof context === 'string') {
target = context;
}
target = target || this._target;
this._debug('Current target is set to:' + target);
// Splits the given property in a list of properties and walks down the config map
// and returns the requested value from the map
every(properties.split('.'), function(key, index, props) {
if(!rootKey) {
rootKey = key;
}
// If the current key is the root key then load in the config object by constructing a filepath,
// the filepath gets constructed based on the current target and the rootkey
if(rootKey === key) {
var filePath = path.join(config._options.root, target, rootKey);
config._debug('Loading following file: ' + filePath);
configHash = config._fs.load(filePath, process.cwd());
result = Array.isArray(configHash) ? configHash : merge(
{},
config._defaultMap[rootKey],
configHash || {},
config._devMap[rootKey]
);
// If the current key is not the root key, reassign result to keep traversing the tree
}else {
result = result[key];
}
//Stop loop when there are no more keys or next result returns undefined
if(isNull(result) || isUndefined(result) || Array.isArray(result)) {
return false;
}else {
return (typeof result === 'object') || !!props[index +1] || result[props[index + 1]];
}
});
if(context) {
if(typeof result === 'string') {
result = template(result, { interpolate: config._options.delimeters })(context);
} else if(Array.isArray(result)) {
return result;
} else if(isObject(result)){
temp = {};
each(Object.keys(result), function(key) {
var keyValue = result[key];
if(typeof keyValue === 'string') {
temp[key] = template(keyValue, { interpolate: config._options.delimeters })(context);
}else {
temp[key] = keyValue;
}
});
result = temp;
}
}
return result;
},
/**
* @param {string} type
* @returns {string} Filepath to the requested config file
*/
path: function (type) {
return path.join(this._options.root, this.env(), type + '.js');
},
/**
* @returns {string} Representing the current environment, taking in to account the node environment, cli arguments, defaults to development
*/
env: function () {
return this._env;
},
getAppConfigTpl: function () {
return '/*jshint quotmark:true */ \"use strict\"; define(function () { return __JSON_CONFIG__; });';
}
};
getConfig.Config = Config;
function getConfig(defaults) {
return new Config(loader, cli, defaults);
}
/**
* @module utils/config
*/
module.exports = getConfig;