Source: json-interface.js

'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;
}