Source: struct.js

'use strict';
var
Legio = require("./std"),
type = require("./type");

/** @module legio/struct */

/**
 * Easily generates a constructor based on given keys, prototype and type definition.
 * @alias module:legio/struct
 * @param {String[]} keys If just keys are given, the array is not necessary.
 * @param {Object} [prototype]
 * @param {Object} [typeDefinition] An object passed to the `type()` function.
 * @returns {constructor}
 *
 * @example
    // A basic struct
    var Point = struct("x", "y", "z");

    var p = new Point(1, 2, 3); // p.x = 1; p.y = 2; p.z = 3;

 * @example
    // Using the type definition
    var TypedPoint = struct(
      ["x", "y", "z"], null,
      { x: Number, y: Number, z: type.OR(type.UNDEFINED, Number) }
    );

    var
    p1 = new TypedPoint(1, 2, 3), // Okay
    p2 = new TypedPoint(1, 2), // Okay
    p3 = new TypedPoint(1, "hello"); // Error
 */
function struct(keysArr, proto, def) {
  var Con, keys = arguments, applyDefaults = true;

  if (Array.is(keysArr)) {
    keys = keysArr;

    if (def) {
      var T = type.is(def) ? def : type(def);

      Con = function () {
        fill(this, keys, arguments);
        if (!T(this)) {
          throw new TypeError("Wrong type passed.");
        }
      };
    }
  }
  else {
    applyDefaults = false;
  }

  if (!Con) {
    Con = function () {
      fill(this, keys, arguments);
    };
  }

  if (applyDefaults && proto) {
    proto.constructor = Con;
    Con.prototype = proto;
  }

  return Con;
}

// A function that fills properties with values
function fill(obj, keys, values) {
  for (var i = 0; i < keys.length; ++i) {
    var key = keys[i];

    if (values[i] !== undefined) {
      obj[key] = values[i];
    }
  }
}

module.exports = struct;