DotEnv.js

/* globals Wsh: false */
/* globals __dirname: false */
/* globals process: false */

(function () {
  if (Wsh && Wsh.DotEnv) return;

  /**
   * DotEnv-like module for WSH (Windows Script Host) that reads/writes environment values in a .env file.
   *
   * @namespace DotEnv
   * @memberof Wsh
   * @requires {@link https://github.com/tuckn/WshModeJs|tuckn/WshModeJs}
   */
  Wsh.DotEnv = {};

  // Shorthands
  var CD = Wsh.Constants;
  var util = Wsh.Util;
  var path = Wsh.Path;
  var os = Wsh.OS;
  var fs = Wsh.FileSystem;

  var merge = util.merge;
  var obtain = util.obtainPropVal;
  var isPlainObject = util.isPlainObject;
  var hasOwnProp = util.hasOwnProp;
  var parseDate = util.parseDateLiteral;

  var dotenv = Wsh.DotEnv;

  /** @constant {string} */
  var MODULE_TITLE = 'WshDotEnv/DotEnv.js';

  var throwErrNonExist = function (functionName, typeErrVal) {
    fs.throwTypeErrorNonExisting(MODULE_TITLE, functionName, typeErrVal);
  };

  // dotenv.envPathDefault {{{
  /**
   * The path of .env directory in the current directory.
   *
   * @name envPathDefault
   * @memberof Wsh.DotEnv
   * @constant {string}
   */
  dotenv.envPathDefault = path.resolve(process.cwd(), '.env'); // }}}

  // dotenv.envPathPortable {{{
  /**
   * The path of .env directory in the directory of WSH script which is specified 1st argument by wscript.exe or cscript.exe.
   *
   * @name envPathPortable
   * @memberof Wsh.DotEnv
   * @constant {string}
   */
  dotenv.envPathPortable = path.resolve(__dirname, '.env'); // }}}

  // dotenv.envPathUsers {{{
  /**
   * The path of .env directory in the user's directory (%USERPROFILE%). Ex. C:\Users\<Name>\.env
   *
   * @name envPathUsers
   * @memberof Wsh.DotEnv
   * @constant {string}
   */
  dotenv.envPathUsers = path.resolve(os.homedir(), '.env'); // }}}

  dotenv.path = dotenv.envPathDefault;

  // dotenv.config {{{
  /**
   * Stores the values in .env file to {@link https://tuckn.net/docs/WshProcess/process.html#env|process.env}
   *
   * @example
   * // If the contents of .env file are following...
   * // # Lines beginning with # are threated as comments,
   * // EMPTY=
   * // JSON={ foo: "bar" }
   * // WHITE_SPACE=  some value
   * // SINGLE_QUOTE='  some value '
   * // DOUBLE_QUOTE="  Some Value "
   * // MULTILINE="new
   * // line"
   * // DIR_7ZIP=C:\Program Files\7-Zip
   * // PATH_CONFIG=.\.config\office-smb-resources.json
   *
   * @example
   * var dotenv = Wsh.DotEnv;
   * dotenv.config();
   *
   * // Ex.1
   * console.dir(process.env);
   * // Outputs: {
   * //   ..
   * //   ...
   * //   EMPTY: '',
   * //   JSON: { foo: "bar" },
   * //   WHITE_SPACE: 'some value',
   * //   SINGLE_QUOTE: '  some value ',
   * //   DOUBLE_QUOTE: '  Some Value ',
   * //   MULTILINE: 'new\nline',
   * //   DIR_7ZIP: 'C:\\Program Files\\7-Zip',
   * //   PATH_CONFIG: '.\\.config\\office-smb-resources.json' }
   *
   * @example
   * // Ex.2 Failed to read .env file
   * var dotenv = Wsh.DotEnv;
   * var result = dotenv.config();
   *
   * if (result.error) throw result.error
   *
   * console.dir(result.parsed);
   * // Outputs: {
   * //   EMPTY: '',
   * //   JSON: { foo: "bar" },
   * //   WHITE_SPACE: 'some value',
   * //   SINGLE_QUOTE: '  some value ',
   * //   DOUBLE_QUOTE: '  Some Value ',
   * //   MULTILINE: 'new\nline',
   * //   DIR_7ZIP: 'C:\\Program Files\\7-Zip',
   * //   PATH_CONFIG: '.\\.config\\office-smb-resources.json' }
   * @function config
   * @memberof Wsh.DotEnv
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.path] - Default: {@link Wsh.DotEnv.envPathDefault}. portable, userProfile, `File Path`
   * @param {boolean} [options.parsesDate=false] - Parses the path as the date literal. See {@link https://tuckn.net/docs/WshUtil/Wsh.Util.html#.parseDateLiteral}.
   * @param {string} [options.encoding] - Default: 'utf-8'. See {@link https://tuckn.net/docs/WshFileSystem/Wsh.FileSystem.html#.readFileSync|Wsh.FileSystem.readFileSync}
   * @returns {object} - Returns an Object with a `parsed` key containing the loaded content or an `error` key if it failed.
   */
  dotenv.config = function (options) {
    var functionName = 'dotenv.config';
    if (!isPlainObject(options)) options = {};

    var envPath = obtain(options, 'path', dotenv.envPathDefault);
    var parsesDate = obtain(options, 'parsesDate', false);

    if (/^portable$/i.test(envPath)) {
      dotenv.path = dotenv.envPathPortable;
    } else if (/^userProfile$/i.test(envPath)) {
      dotenv.path = dotenv.envPathUsers;
    } else {
      if (parsesDate) dotenv.path = parseDate(envPath);
      else dotenv.path = envPath;
    }

    try {
      if (!fs.existsSync(dotenv.path)) {
        throwErrNonExist(functionName, dotenv.path);
      }

      var encoding = obtain(options, 'encoding', CD.ado.charset.utf8);
      var envList = fs.readFileSync(dotenv.path, { encoding: encoding });
      var RE_NEWLINES = /\n|\r|\r\n/;
      var rows = envList.split(RE_NEWLINES);
      var parsed = {};

      rows.forEach(function (row) {
        var posNumberSign = row.indexOf('#');
        if (posNumberSign === 0) return; // Comment out

        var posEq = row.indexOf('=');
        if (posEq === -1) return; // @TODO Parse Error?

        var key = row.slice(0, posEq);
        if (hasOwnProp(process.env, key)) return;

        var val = row.slice(posEq + 1).trim();

        if (/^'[\s\S]*'$/.test(val)) { // Ex. '  some value '
          val = val.match(/^'([\s\S]*)'$/)[1].replace(/\\n/g, '\n');
        } else if (/^"[\s\S]*"$/.test(val)) { //  Ex. "  Some Value "
          val = val.match(/^"([\s\S]*)"$/)[1].replace(/\\n/g, '\n');
        }

        parsed[key] = val;
      });

      merge(process.env, parsed);
      return parsed;
    } catch (e) {
      return { error: e };
    }
  }; // }}}
})();

// vim:set foldmethod=marker commentstring=//%s :