All files / src/config parse.js

87.72% Statements 50/57
85.71% Branches 60/70
100% Functions 3/3
87.04% Lines 47/54
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141        1x 1x     1x 30x   30x 3x 1x   2x       29x       137x       137x     117x         117x       35x         82x       117x   1x         116x       117x         468x 117x 114x     117x 19x       117x               117x 17x 17x   17x     117x                 117x 2x 1x         116x 18x 1x         115x     72x   107x     72x 2x                 113x 4x 4x 3x   110x     22x    
// parse.js
// Reads an schema and retrieves the proper options from it
 
// Errors specifics to this submodule
const OptionsError = require('./errors');
const path = require('path');
 
 
module.exports = async (schema, arg = {}, env= {}, parent = {}) => {
  const options = {};
 
  if (typeof arg !== 'object') {
    if (!schema.__root) {
      throw new OptionsError('/server/options/notobject');
    }
    arg = { [schema.__root]: arg };
  }
 
  // Loop each of the defined variables
  for (let key in schema) {
 
    // RETRIEVAL
    // Make the definition local so it's easier to handle
    const def = schema[key];
    let value;
 
    // Skip the control variables such as '__root'
    if (/^\_\_/.test(key)) continue;
 
    // Make sure we are dealing with a valid definition
    Iif (typeof def !== 'object') {
      throw new Error('Invalid option definition: ' + JSON.stringify(def));
    }
 
    // Decide whether to use the argument or not
    if (def.arg === false) {
 
      // No argument expected but one was passed
      // Should this throw or not?
      Iif (arg[key]) {
        console.log((new OptionsError('/server/options/noarg')).message);
        // throw new OptionsError('/server/options/noarg', { key });
      }
    } else {
      def.arg = def.arg === true ? key : def.arg || key;
    }
 
    // Decide whether to use the environment or not
    if (def.env === false) {
      // No argument expected but one was passed
      Iif (env[key.toUpperCase()]) {
        console.log((new OptionsError('/server/options/noenv')).message);
        // throw new OptionsError('/server/options/noenv', { key });
      }
    } else {
      def.env = (def.env === true ? key : def.env || key).toUpperCase();
    }
 
    // List of possibilities, from HIGHER preference to LOWER preference
    const possible = [
      env[def.env],
      arg[def.arg],
      parent[def.inherit],
      def.default
    ].filter(value => typeof value !== 'undefined');
    if (possible.length) {
      value = possible[0];
    }
 
    if (def.find) {
      value = await def.find(value, { arg, env, parent, schema });
    }
 
    // Extend the base object or user object with new values if these are not set
    Iif (def.extend && (typeof value === 'undefined' || typeof value === 'object')) {
      if (typeof value === 'undefined') {
        value = {};
      }
      Object.assign(value, def.default, value);
    }
 
    // Normalize the "public" pub
    if (def.file && typeof value === 'string') {
      Eif (!path.isAbsolute(value)) {
        value = path.join(process.cwd(), value);
      }
      value = path.normalize(value);
    }
 
    Iif (def.clean) {
      value = def.clean(value, { arg, env, parent, schema });
    }
 
 
 
 
    // VALIDATION
    // Validate that it is set
    if (def.required) {
      if (typeof value === 'undefined') {
        throw new OptionsError('/server/options/required', { key });
      }
      // TODO: check that the file and folder exist
    }
 
    if (def.enum) {
      if (!def.enum.includes(value)) {
        throw new OptionsError('/server/options/enum', { key, value, possible: def.enum });
      }
    }
 
    // Validate the type (only if there's a value)
    if (def.type && value) {
 
      // Parse valid types into a simple array of strings: ['string', 'number']
      def.type = (def.type instanceof Array ? def.type : [def.type])
        // pulls up the name for primitives such as String, Number, etc
        .map(one => (one.name ? one.name : one).toLowerCase());
 
      // Make sure it is one of the valid types
      if (!def.type.includes(typeof value)) {
        throw new OptionsError('/server/options/type', {
          key,
          expected: def.type,
          received: typeof value,
          value
        });
      }
    }
 
    if (def.validate) {
      let ret = def.validate(value, def, options);
      if (ret instanceof Error) throw ret;
      if (!ret) throw new OptionsError('/server/options/validate', { key, value });
    }
    options[key] = value;
  }
 
  return options;
};