<commit_before>module.exports = function (dex) {
  /**
   *
   * This module provides various configuration routines.
   *
   * @module dex/config
   *
   */
  var config = {};

  /**
   *
   * This routine supports a shorthand notation allowing the
   * user to specify deeply nested configuration options without
   * having to deal with nested json structures.
   *
   * @param {object} config The configuration to expand.
   * @returns {object} An object containing the expanded configuration.
   *   The original configuration is left untouched.
   * @memberof dex/config
   *
   * @example
   *
   * {
   *   'cell' : {
   *     'rect' : {
   *       'width' : 10,
   *       'height' : 20
   *     }
   *   }
   * }
   *
   * becomes easier to read when expressed as:
   *
   * {
   *   'cell.rect.width'            : 10,
   *   'cell.rect.height'           : 20,
   * }
   *
   */
  config.expand = function expand(cfg) {
    var ecfg= cfg;
    var name, ci;
    var expanded = {};

    // We have nothing, return nothing.
    if (!ecfg) {
      return undefined;
    }

    for (var name in ecfg) {
      if (ecfg.hasOwnProperty(name)) {
        // Name contains hierarchy:
        if (name && name.indexOf('.') > -1) {
          expanded[name] = ecfg[name];
          dex.object.setHierarchical(expanded, name,
            dex.object.clone(expanded[name]), '.');
          delete expanded[name];
        }
        // Simple name
        else {
          // If the target is an object with no children, clone it.
          if (dex.object.isEmpty(ecfg[name])) {
            //dex.console.log("SET PRIMITIVE: " + name + "=" + ecfg[name]);
            expanded[name] = dex.object.clone(ecfg[name]);
            //expanded[name] = ecfg[name];
          }
          else {
            // CSV is a special case.  Older WebKit browsers such as
            // that used by JavaFX can't seem to detect the contructor
            // so i build in a special workaround for any attribute
            // named csv to be copied as-is.
            if (name == "csv") {
              // Link to the old csv.
              expanded[name] = ecfg[name];
              // Allocate an entire new csv.
              //expanded[name] = new dex.csv(ecfg[name]);
            }
            else if (ecfg[name].constructor !== undefined &&
              ecfg[name].constructor.name === "csv") {
              // Link to old csv:
              expanded[name] = ecfg[name];
              //expanded[name] = new dex.csv(ecfg[name]);
            }
            else {
              //dex.console.log("SET OBJECT: " + name + " to the expansion of", ecfg[name]);
              expanded[name] = dex.config.expand(ecfg[name]);
            }
          }
        }
      }
    }

    ecfg = undefined;
    //dex.console.log("CONFIG", ecfg, "EXPANDED", expanded);
    return expanded;
  };

  /**
   *
   * This routine will take one or more objects and expand each one
   * via dex.config.expand.  The objects will then be overlaid on top
   * of each other.  Objects are supplied in descending precedence;
   * meaning that object conflicts will be resolved by taking the
   * value from the first supplied object defining it within the
   * method call.
   *
   * @param {object} top - The top object hierarchy.
   * @param {object} bottom - The bottom, base object hierarchy.
   * @returns {object} - A new object representing the expanded
   * and overlaid objects.  The objects supplied in the method
   * call will not be modified.
   * @memberof dex/config
   *
   * @example
   *
   * Given:
   *
   * var first = {
   *   "name": "first",
   *   "screen.width" : 800
   * };
   *
   * var second = {
   *   "name": "second",
   *   "screen.width": 100,
   *   "screen.height": 600,
   *   "secondField": "Only in 2nd"
   * };
   *
   * var third = {
   *   "name": "third",
   *   "screen.width": 333,
   *   "screen.height": 333,
   *   "thirdField": "Only in 3rd"
   * };
   *
   * Calling:
   *
   * var combined = dex.config.expandAndOverlay(first, second, third);
   *
   * Yields a combined value of:
   *
   * {
   *   "name": "first",
   *   "screen": {
   *     "width": 800,
   *     "height": 600
   *   },
   *   "secondField": "Only in 2nd",
   *   "thirdField" : "Only in 3rd"
   * }
   *
   */
  config.expandAndOverlay = function expandAndOverlay() {
    switch (arguments.length) {
      case 0: {
        return {};
      }
      case 1: {
        if (arguments[0] === undefined) {
          return {};
        }
        return expand(arguments[0]);
      }
      default: {
        var expanded = [];
        var i;
        for (i = 0; i < arguments.length; i++) {
          if (arguments[i] === undefined) {
            expanded.push({});
          }
          else {
            expanded.push(dex.config.expand(arguments[i]));
          }
        }

        var overlay = dex.object.overlay(expanded[expanded.length - 2],
          expanded[expanded.length - 1]);

        for (i = arguments.length - 3; i >= 0; i--) {
          overlay = dex.object.overlay(expanded[i], overlay);
        }

        return overlay;
      }
    }
  };

  /**
   *
   * Return the configuration for a font after the user's
   * customizations have been applied.
   *
   * @param {FontSpec} options - The user customizations.
   * @param {string} [options.family="sans-serif"] - The font family.
   * @param {number} [options.size=14] - The font size.
   * @param {string} [options.style="normal"] - The font style.
   * @param {string} [options.weight="normal"] - The font weight.
   * @param {string} [options.variant="normal"] - The font variant.
   *
   * @returns {object} - An object containing the font's specifications<commit_msg>Added formatter function for assisting in label formatting<commit_after>module.exports = function (dex) {
  /**
   *
   * This module provides various configuration routines.
   *
   * @module dex/config
   *
   */
  var config = {};

  /**
   *
   * This routine supports a shorthand notation allowing the
   * user to specify deeply nested configuration options without
   * having to deal with nested json structures.
   *
   * @param {object} config The configuration to expand.
   * @returns {object} An object containing the expanded configuration.
   *   The original configuration is left untouched.
   * @memberof dex/config
   *
   * @example
   *
   * {
   *   'cell' : {
   *     'rect' : {
   *       'width' : 10,
   *       'height' : 20
   *     }
   *   }
   * }
   *
   * becomes easier to read when expressed as:
   *
   * {
   *   'cell.rect.width'            : 10,
   *   'cell.rect.height'           : 20,
   * }
   *
   */
  config.expand = function expand(cfg) {
    var ecfg= cfg;
    var name, ci;
    var expanded = {};

    // We have nothing, return nothing.
    if (!ecfg) {
      return undefined;
    }

    for (var name in ecfg) {
      if (ecfg.hasOwnProperty(name)) {
        // Name contains hierarchy:
        if (name && name.indexOf('.') > -1) {
          expanded[name] = ecfg[name];
          dex.object.setHierarchical(expanded, name,
            dex.object.clone(expanded[name]), '.');
          delete expanded[name];
        }
        // Simple name
        else {
          // If the target is an object with no children, clone it.
          if (dex.object.isEmpty(ecfg[name])) {
            //dex.console.log("SET PRIMITIVE: " + name + "=" + ecfg[name]);
            expanded[name] = dex.object.clone(ecfg[name]);
            //expanded[name] = ecfg[name];
          }
          else {
            // CSV is a special case.  Older WebKit browsers such as
            // that used by JavaFX can't seem to detect the contructor
            // so i build in a special workaround for any attribute
            // named csv to be copied as-is.
            if (name == "csv") {
              // Link to the old csv.
              expanded[name] = ecfg[name];
              // Allocate an entire new csv.
              //expanded[name] = new dex.csv(ecfg[name]);
            }
            else if (ecfg[name].constructor !== undefined &&
              ecfg[name].constructor.name === "csv") {
              // Link to old csv:
              expanded[name] = ecfg[name];
              //expanded[name] = new dex.csv(ecfg[name]);
            }
            else {
              //dex.console.log("SET OBJECT: " + name + " to the expansion of", ecfg[name]);
              expanded[name] = dex.config.expand(ecfg[name]);
            }
          }
        }
      }
    }

    ecfg = undefined;
    //dex.console.log("CONFIG", ecfg, "EXPANDED", expanded);
    return expanded;
  };

  /**
   *
   * This routine will take one or more objects and expand each one
   * via dex.config.expand.  The objects will then be overlaid on top
   * of each other.  Objects are supplied in descending precedence;
   * meaning that object conflicts will be resolved by taking the
   * value from the first supplied object defining it within the
   * method call.
   *
   * @param {object} top - The top object hierarchy.
   * @param {object} bottom - The bottom, base object hierarchy.
   * @returns {object} - A new object representing the expanded
   * and overlaid objects.  The objects supplied in the method
   * call will not be modified.
   * @memberof dex/config
   *
   * @example
   *
   * Given:
   *
   * var first = {
   *   "name": "first",
   *   "screen.width" : 800
   * };
   *
   * var second = {
   *   "name": "second",
   *   "screen.width": 100,
   *   "screen.height": 600,
   *   "secondField": "Only in 2nd"
   * };
   *
   * var third = {
   *   "name": "third",
   *   "screen.width": 333,
   *   "screen.height": 333,
   *   "thirdField": "Only in 3rd"
   * };
   *
   * Calling:
   *
   * var combined = dex.config.expandAndOverlay(first, second, third);
   *
   * Yields a combined value of:
   *
   * {
   *   "name": "first",
   *   "screen": {
   *     "width": 800,
   *     "height": 600
   *   },
   *   "secondField": "Only in 2nd",
   *   "thirdField" : "Only in 3rd"
   * }
   *
   */
  config.expandAndOverlay = function expandAndOverlay() {
    switch (arguments.length) {
      case 0: {
        return {};
      }
      case 1: {
        if (arguments[0] === undefined) {
          return {};
        }
        return expand(arguments[0]);
      }
      default: {
        var expanded = [];
        var i;
        for (i = 0; i < arguments.length; i++) {
          if (arguments[i] === undefined) {
            expanded.push({});
          }
          else {
            expanded.push(dex.config.expand(arguments[i]));
          }
        }

        var overlay = dex.object.overlay(expanded[expanded.length - 2],
          expanded[expanded.length - 1]);

        for (i = arguments.length - 3; i >= 0; i--) {
          overlay = dex.object.overlay(expanded[i], overlay);
        }

        return overlay;
      }
    }
  };

  config.getFormatter = function getFormatter(name, type) {
    dex.console.log("getFormatter", name);
    var formatters = {
      "none" : function(value, i) {
        dex.console.log("default formatting of: '" + value + "'");
        return value;
      },

      "abbreviate" : function(value) {
        if (type === "number") {
          if (value > 1000000000000000000) {
            return parseInt(value / 1000000000000000) + "E"
          }
          else if (value > 1000000000000000) {
            return parseInt(value / 1000000000000) + "P"
          }
          else if (value > 1000000000000) {
            return parseInt(value / 1000000000) + "T"
          }
          else if (value > 1000000000) {
            return parseInt(value / 1000000000) + "G"
          }
          else if (value > 1000000) {
            return parseInt(value / 1000000) + "M"
          }
          else if (value > 1000) {
            return parseInt(value / 1000) + "K"
          }
          else {
            return value;
          }
        }
        // Non numbers simply pass thru
        else {
          return value;
        }
      }
    };
    if (name === undefined || name.length == 0) {
      return formatters.default;
    }
    else if (formatters[name] !== undefined) {
      return formatters[name];
    }
    else {
      return name;
    }
  };

  /**
   *
   * Return the configuration for a font after the user's
   * customizations have been applied.
   *
   * @param {FontSpec} options - The user customizations.
   * @param {string} [options.family="sans-serif"] - The font family.
   * @param {number} [options.size=14] - The font size.
   * @param {string} [options.style="normal"] - The font style.
   * @param {string} [options.weight="normal"] - The font weight.
   * @param {string} [options.variant="normal"] - The font variant.
   *
   * @returns {object} - An object containing the font's specifications
{'fraction_non_alphanumeric': 0.13177148194271482, 'fraction_numerical': 0.017123287671232876, 'mean_word_length': 1.772168284789644, 'pattern_counts': {'":': 28, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 8, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4361441', 'n_tokens_mistral': 4118, 'n_tokens_neox': 3853, 'n_words': 1080}