'use strict';
// # json-config-interface
// **Loads a config file from a given path. Generates an API based on permissions requested.**
/**
* Supplies the root menu for program opperation,
* if commandline arguments supplied, moves user to the correct jump point.
* @author Jon L-W
* @module json-config-interface
* @requires module:fs
* @requires module:bluebird
*/
// ## Dependancies
// - fs
// - bluebird
var fs = require('fs')
, Q = require('q')
// `configCache` Object that stores loaded JSON config files indexed by path,
// Anywhere a config file is required, the same data object is returned to act upon,
// ensuring data integrity across a project.
, configCache = {}
// base path used to prepend `path` param provided to `get()`
, configBasePath = '';
// ## Exports
// - `setDir` - specify base directory to load configs from
// - `get` - get a configuration files data and methods to act upon it
// - `purge` - cache clearing operations
/**
* Sets the base working directory to load JSON config files from.
* @param {string} basePath=false - the path to the directory to load config files in
* @returns {bool} true on good directory, false on bad directory
* @throws {badParam} basePath param not provided, or not string
* @throws {nonDir} basePath does not point to a directory
*/
// # exports.setDir
module.exports.setDir = function(basePath = false){
// if not a good parameter
if(!basePath || typeof basePath !== 'string'){
// throw **badParam**
throw new Error('badParam');
return false;
}
try {
// Query provided path string
var stats = fs.lstatSync(basePath);
// if it is a directory
if (stats.isDirectory()) {
// set basePath to module var for usage during exports.get
configBasePath = basePath;
return true;
}
else{
// throw **nonDir**
throw new Error('nonDir')
}
}
// throw any lstatSync errors up to caller
catch (err) {
throw err;
return false;
}
}
/**
* @typedef ConfigAPI
* @type Object
* @property {object} data - the parsed JSON config file
* @property {function} [save] - method to save any changes back to JSON config file
* @property {function} [revert] - rollback any changes made in .data to state when it was loaded
*/
/**
* Request a config file be loaded, assign API methods based on permision arg.
* @param {string} path - The location of the config file.
* @param {string} [permision='read'] - If `"write"` the config api will be returned with save and revert methods
* @param {boolean} [noCache=false] - If `true` will ignore cached instances of config file
* @return {Promise<ConfigAPI|Error>}
* @throws {badParam} path param undefined or not a string
* @throws {fs.stat.err} any errors during fs.stat
* @throws {fs.readFileSync.err} any errors during fs.readFileSync
*/
// # exports.get
module.exports.get = function(path, permision='read', noCache=false){
var deferred = Q.defer()
// `configAPI` object to be returned containing the JSON object `.data` and if `permision=='write'` the `save()` & `revert()` methods that act upon the
, configAPI = {};
// build full path string using configBasePath and path
if(path!==undefined && typeof path === 'string'){
// if path name supplied without `.json` extension, add it
if(path.indexOf('.json')===-1){
path += '.json';
}
// if path contains `./` at it's begining, don't add base class, as this is a relative path
// else
var configPath = (/^\.\//.exec(path)!==null ? path : (path[0] === '/' ? configBasePath + path : configBasePath + '/' + path))
}
else{
deferred.reject(new Error('badParam'));
return deferred.promise;
}
console.log(configPath)
// define `configAPI.data` with `false`
configAPI.data = false;
// if write permmision set, attach the save method to configAPI
if(permision==='write'){
// setup write API
var original = false
// ## `save()`
, save = function(){
var saveDeferred = Q.defer();
// Stringify to JSON with indentation of 2 spaces and write to config file
fs.writeFile(configPath, JSON.stringify(configAPI.data, null, 2), function(err){
if(err){
// if write error, reject promise with `err` object
saveDeferred.reject(err);
}
else{
// if write success, resolve with `true` val
saveDeferred.resolve(true);
}
});
return saveDeferred.promise;
}
// ## `revert()`
, revert = function(){
configAPI.data = original;
return configAPI;
}
// assign save and revert to configAPI
configAPI.save = save;
configAPI.revert = revert;
}
// ## cache
// If config has already been loaded, return the instance of it's parsed object from cache
if(configCache[configPath]!==undefined && !noCache){
// set property `configAPI.data` as the previously parsed JSON object
configAPI.data = configCache[configPath];
original = function(){ return JSON.parse(JSON.stringify(configCache[configPath])); }();
// return resolved
return Q(configAPI);
}
// ## config file load & parse
// check config file exists, if it does load or return with error
fs.stat(configPath, function(err, stat){
// if no error in file stat
if(err == null) {
// try to read file and parse JSON to var
try{
var jsonContents = fs.readFileSync(configPath, 'utf8');
var parsedJSONdata = JSON.parse(jsonContents);
}
// catch any errors during read or parse, if so reject with error
catch(err) {
deferred.reject(err);
}
// if good load and parse
if(parsedJSONdata!==undefined){
// add loaded config to cache
if(!noCache){
configCache[configPath] = parsedJSONdata;
}
// assign fileData to property data of the config object
configAPI.data = parsedJSONdata;
// make clone of config file to be used if revert method is called of API
configAPI.original = JSON.parse(JSON.stringify(parsedJSONdata));
// resolve with the built `configAPI`
deferred.resolve(configAPI);
}
}
// catch all for errors in reading config
else{
deferred.reject(err);
}
});
return deferred.promise;
}
/**
* Cleans entire cache or cleans cache of specific file if path provided
* @param {string} [path] The location of the config file to purge from cache. If not provided, cleans entire cache
* @return {boolean} true for successful purges, false if path supplied and not found in cache
*/
// # exports.purge
module.exports.purge = function(path){
// if no `path` then clean entire cache
if(path===undefined){
configCache = {};
return true;
}
// if path given and index in the object not undefined.
if(configCache[path]!==undefined){
// set the cache for path to undefined
configCache[path] = undefined;
return true;
}
return false;
}