<commit_before>var validator = require("validator"),
    ValidatorPrototype = validator.Validator.prototype,
    object = require("object-additions").object;

var externalValidator = new validator.Validator();

function hasValue(value) {
  return !(undefined === value || null === value || "" === value);
}

function Validator(fieldname, label) {
  var stack = [];
  var fieldLabel = label || fieldname;

  this.name = fieldname;
  this.__required = false;

  this.add = function(func) {
    stack.push(func);
    return this;
  };

  this.run = function(formData) {
    var errors = [];

    stack.forEach(function(validate) {
      validate.getValue = function(name) {
        return formData[name];
      };

      var value = formData[fieldname];

      if (false === this.__required && false === hasValue(value)) {
        return;
      }

      try {
        validate(value);

      } catch(e) {
        var message = (e.message || e).toString().replace("%s", fieldLabel);
        errors.push(message);
      }
    }.bind(this));

    if (errors.length) return errors;
  };
}

const MESSAGES = {
  isEmail: "%s is not an email address",
  isUrl: "%s is not a URL",
  isIP: "%s is not an IP address",
  isAlpha: "%s contains non-letter characters",
  isAlphanumeric: "%s contains non alpha-numeric characters",
  isNumeric: "%s is not numeric",
  isLowercase: "%s contains uppercase letters",
  isUppercase: "%s contains lowercase letters",
  isInt: "%s is not an integer",
  notNull: "%s is null",
  isNull: "%s is not null",
  notEmpty: "%s has no value or is only whitespace",
  contains: "%s does not contain required characters",
  notContains: "%s contains invalid characters"
};

Object.keys(ValidatorPrototype).forEach(function(name) {
  if (name.match(/^(equals|check|validate|assert|error|len|isNumeric|isDecimal|isFloat|regex|notRegex|is|not)$/))
    return;

  Validator.prototype[name] = function() {
    var args = Array.prototype.slice.call(arguments);

    var message = undefined;

    if (args.length) {
      switch(name) {
        case "contains":
        case "notContains":
          message = args[1];
          break;
        default:
          message = args[0];
      }
    }

    message = message || MESSAGES[name];

    return this.add(function(value) {
      if (typeof value == "undefined" || value === undefined) {
        return value;
      }
      return ValidatorPrototype[name].apply(externalValidator.check(value, message), args);
    });
  };
});

Validator.prototype.equals = function(other, message) {
  if (object.isString(other) && other.match(/^field::/)) {
    this.__required = true;
  }

  return this.add(function(value) {
    // If other is a field token (field::fieldname), grab the value of fieldname
    // and use that as the OTHER value.
    if (object.isString(other) && other.match(/^field::/)) {
      other = arguments.callee.getValue(other.replace(/^field::/, ''));
    }
    if (value != other) {
      throw new Error(message || "%s does not equal " + String(other));
    }
  });
};

// node-validator's numeric validator seems unintuitive. All numeric values should be valid, not just int.
Validator.prototype.isNumeric = function(message) {
  return this.add(function(value) {
    if (object.isNumber(value) || (object.isString(value) && value.match(/^[-+]?[0-9]*\.?[0-9]+$/))) {
    } else {
      throw new Error(message || "%s is not a number");
    }
  });
};

// node-validator's decimal/float validator incorrectly thinks Ints are valid.
Validator.prototype.isDecimal = function(message) {
  return this.add(function(value) {
    if ((object.isNumber(value) && value % 1 == 0) || (object.isString(value) && value.match(/^[-+]?[0-9]*\.[0-9]+$/))) {
    } else {
      throw new Error(message || "%s is not a decimal");
    }
  });
};

Validator.prototype.isFloat = Validator.prototype.isDecimal;

Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers, message) {
  // regex(/pattern/)
  // regex(/pattern/, "message")
  // regex("pattern")
  // regex("pattern", "modifiers")
  // regex("pattern", "message")
  // regex("pattern", "modifiers", "message")

  if (pattern instanceof RegExp) {
    if (object.isString(modifiers) && modifiers.match(/^[gimy]+$/)) {
      throw new Error("Invalid arguments: `modifiers` can only be passed in if `pattern` is a string.");
    }

    message = modifiers;
    modifiers = undefined;

  } else if (object.isString(pattern)) {
    if (arguments.length == 2 && !modifiers.match(/^[gimy]+$/)) {
      // 2nd arg doesn't look like modifier flags, it's the message (might also be undefined)
      message = modifiers;
      modifiers = undefined;
    }
    pattern = new RegExp(pattern, modifiers);
  }

  return this.add(function(value) {
    if (pattern.test(value) === false) {
      throw new Error(message || "%s has invalid characters");
    }
  });
};

Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers, message) {
  // notRegex(/pattern/)
  // notRegex(/pattern/, "message")
  // notRegex("pattern")
  // notRegex("pattern", "modifiers")
  // notRegex("pattern", "message")
  // notRegex("pattern", "modifiers", "message")

  if (pattern instanceof RegExp) {
    if (object.isString(modifiers) && modifiers.match(/^[gimy]+$/)) {
      throw new Error("Invalid arguments: `modifiers` can only be passed in if `pattern` is a string.");
    }

    message = modifiers;
    modifiers = undefined;

  } else if (object.isString(pattern)) {
    if (arguments.length == 2 && !modifiers.match(/^[gimy]+$/)) {
      // 2nd arg doesn't look like modifier flags, it's the message (might also be undefined)
      message = modifiers;
      modifiers = undefined;
    }
    pattern = new RegExp(pattern, modifiers);
  }

  return this.add(function(value) {
    if (pattern.test(value) === true) {
      throw new Error(message || "%s has invalid characters");
    }
  });
};

Validator.prototype.required = function(placeholderValue, message) {
  this.__required = true;
  return this.add(function(value) {
    if (!hasValue(value) || value == placeholderValue) {
      throw new Error(message || "%s is required");
    }
  });
};

Validator.prototype.minLength = function(length, message) {
  return this.add(function(value) {
    if (value.toString().length < length) {
      throw new Error(message || "%s is too short");
    }
  });
};

Validator.prototype.maxLength = function(length, message) {
  return this.add(function(value) {
    if (value.toString().length > length) {
      throw new Error(message || "%s is too long");
    }
  });
};

Validator.prototype.custom = function(func, message) {
  return this.add(function(value) {
    try {
      func(value);
    } catch (e) {
      throw new Error(message || e.message || "%s is invalid");
    }
  });
};

module.exports = Validator;
<commit_msg>Remove isNull and notNull validators -- they were useless --> use .required() instead.<commit_after>var validator = require("validator"),
    ValidatorPrototype = validator.Validator.prototype,
    object = require("object-additions").object;

var externalValidator = new validator.Validator();

function hasValue(value) {
  return !(undefined === value || null === value || "" === value);
}

function Validator(fieldname, label) {
  var stack = [];
  var fieldLabel = label || fieldname;

  this.name = fieldname;
  this.__required = false;

  this.add = function(func) {
    stack.push(func);
    return this;
  };

  this.run = function(formData) {
    var errors = [];

    stack.forEach(function(validate) {
      validate.getValue = function(name) {
        return formData[name];
      };

      var value = formData[fieldname];

      if (false === this.__required && false === hasValue(value)) {
        // If this field is not required and it doesn't have a value,
        // don't run the validator on its value.
        return;
      }

      try {
        validate(value);

      } catch(e) {
        var message = (e.message || e).toString().replace("%s", fieldLabel);
        errors.push(message);
      }
    }.bind(this));

    if (errors.length) return errors;
  };
}

const MESSAGES = {
  isEmail: "%s is not an email address",
  isUrl: "%s is not a URL",
  isIP: "%s is not an IP address",
  isAlpha: "%s contains non-letter characters",
  isAlphanumeric: "%s contains non alpha-numeric characters",
  isNumeric: "%s is not numeric",
  isLowercase: "%s contains uppercase letters",
  isUppercase: "%s contains lowercase letters",
  isInt: "%s is not an integer",
  notEmpty: "%s has no value or is only whitespace",
  contains: "%s does not contain required characters",
  notContains: "%s contains invalid characters"
};

Object.keys(ValidatorPrototype).forEach(function(name) {
  if (name.match(/^(equals|check|validate|assert|error|len|isNumeric|isDecimal|isFloat|regex|notRegex|is|not|notNull|isNull)$/))
    return;

  Validator.prototype[name] = function() {
    var args = Array.prototype.slice.call(arguments);

    var message = undefined;

    if (args.length) {
      switch(name) {
        case "contains":
        case "notContains":
          message = args[1];
          break;
        default:
          message = args[0];
      }
    }

    message = message || MESSAGES[name];

    return this.add(function(value) {
      return ValidatorPrototype[name].apply(externalValidator.check(value, message), args);
    });
  };
});

Validator.prototype.equals = function(other, message) {
  if (object.isString(other) && other.match(/^field::/)) {
    this.__required = true;
  }

  return this.add(function(value) {
    // If other is a field token (field::fieldname), grab the value of fieldname
    // and use that as the OTHER value.
    if (object.isString(other) && other.match(/^field::/)) {
      other = arguments.callee.getValue(other.replace(/^field::/, ''));
    }
    if (value != other) {
      throw new Error(message || "%s does not equal " + String(other));
    }
  });
};

// node-validator's numeric validator seems unintuitive. All numeric values should be valid, not just int.
Validator.prototype.isNumeric = function(message) {
  return this.add(function(value) {
    if (object.isNumber(value) || (object.isString(value) && value.match(/^[-+]?[0-9]*\.?[0-9]+$/))) {
    } else {
      throw new Error(message || "%s is not a number");
    }
  });
};

// node-validator's decimal/float validator incorrectly thinks Ints are valid.
Validator.prototype.isDecimal = function(message) {
  return this.add(function(value) {
    if ((object.isNumber(value) && value % 1 == 0) || (object.isString(value) && value.match(/^[-+]?[0-9]*\.[0-9]+$/))) {
    } else {
      throw new Error(message || "%s is not a decimal");
    }
  });
};

Validator.prototype.isFloat = Validator.prototype.isDecimal;

Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers, message) {
  // regex(/pattern/)
  // regex(/pattern/, "message")
  // regex("pattern")
  // regex("pattern", "modifiers")
  // regex("pattern", "message")
  // regex("pattern", "modifiers", "message")

  if (pattern instanceof RegExp) {
    if (object.isString(modifiers) && modifiers.match(/^[gimy]+$/)) {
      throw new Error("Invalid arguments: `modifiers` can only be passed in if `pattern` is a string.");
    }

    message = modifiers;
    modifiers = undefined;

  } else if (object.isString(pattern)) {
    if (arguments.length == 2 && !modifiers.match(/^[gimy]+$/)) {
      // 2nd arg doesn't look like modifier flags, it's the message (might also be undefined)
      message = modifiers;
      modifiers = undefined;
    }
    pattern = new RegExp(pattern, modifiers);
  }

  return this.add(function(value) {
    if (pattern.test(value) === false) {
      throw new Error(message || "%s has invalid characters");
    }
  });
};

Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers, message) {
  // notRegex(/pattern/)
  // notRegex(/pattern/, "message")
  // notRegex("pattern")
  // notRegex("pattern", "modifiers")
  // notRegex("pattern", "message")
  // notRegex("pattern", "modifiers", "message")

  if (pattern instanceof RegExp) {
    if (object.isString(modifiers) && modifiers.match(/^[gimy]+$/)) {
      throw new Error("Invalid arguments: `modifiers` can only be passed in if `pattern` is a string.");
    }

    message = modifiers;
    modifiers = undefined;

  } else if (object.isString(pattern)) {
    if (arguments.length == 2 && !modifiers.match(/^[gimy]+$/)) {
      // 2nd arg doesn't look like modifier flags, it's the message (might also be undefined)
      message = modifiers;
      modifiers = undefined;
    }
    pattern = new RegExp(pattern, modifiers);
  }

  return this.add(function(value) {
    if (pattern.test(value) === true) {
      throw new Error(message || "%s has invalid characters");
    }
  });
};

Validator.prototype.required = function(placeholderValue, message) {
  this.__required = true;
  return this.add(function(value) {
    if (!hasValue(value) || value == placeholderValue) {
      throw new Error(message || "%s is required");
    }
  });
};

Validator.prototype.minLength = function(length, message) {
  return this.add(function(value) {
    if (value.toString().length < length) {
      throw new Error(message || "%s is too short");
    }
  });
};

Validator.prototype.maxLength = function(length, message) {
  return this.add(function(value) {
    if (value.toString().length > length) {
      throw new Error(message || "%s is too long");
    }
  });
};

Validator.prototype.custom = function(func, message) {
  return this.add(function(value) {
    try {
      func(value);
    } catch (e) {
      throw new Error(message || e.message || "%s is invalid");
    }
  });
};

module.exports = Validator;
<|endoftext|>


<commit_before>var net = require('net');

var validators = {
    isEmail: function(str) {
        return str.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
    },
    isUrl: function(str) {
        return str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/i) || str.length > 2083;
    },
    isIp: function(str) {
        // net.isIp requires node >= 0.3.0
        var modernNode = typeof net.isIP === 'function';
        var method = modernNode? this.isIpNet : this.isIpManual;
        return method(str);
    },
    isIpNet: function(str) {
        return net.isIP(str) !== 0;
    },
    isIpManual: function(str) {
        return str.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/);
    },
    isAlpha: function(str) {
        return str.match(/^[a-zA-Z]+$/);
    },
    isAlphanumeric: function(str) {
        return str.match(/^[a-zA-Z0-9]+$/);
    },
    isNumeric: function(str) {
        return str.match(/^-?[0-9]+$/);
    },
    isLowercase: function(str) {
        return str.match(/^[a-z0-9]+$/);
    },
    isUppercase: function(str) {
        return str.match(/^[A-Z0-9]+$/);
    },
    isInt: function(str) {
        return str.match(/^(?:-?(?:[0-9][0-9]*)(?:\.?0+)?)$/);
    },
    isDecimal: function(str) {
        return str !== '' && str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/);
    },
    isDivisibleBy: function(str, n) {
        return !(parseFloat(str) % n);
    },
    notNull: function(str) {
        return str !== '';
    },
    isNull: function(str) {
        return str === '';
    }
};

var Validator = exports.Validator = function() {}

Validator.prototype.check = function(str, fail_msg) {
    this.str = (str == null || (isNaN(str) && str.length == undefined)) ? '' : str;
    // Convert numbers to strings but keep arrays/objects
    if (typeof this.str == 'number') {
      this.str += '';
    }
    this.msg = fail_msg;
    this._errors = this._errors || [];
    return this;
}

// Helper function to avoid duplication of code
function toDateTime(date) {
    if (date instanceof Date) {
      return date;
    }
    var intDate = Date.parse(date);
    if (isNaN(intDate)) {
        return null;
    }
    return new Date(intDate);
}

// Convert to date without the time component
function toDate(date) {
    if (!(date instanceof Date)) {
      date = toDateTime(date);
    }
    if (!date) {
      return null;
    }
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    return date;
}

//Create some aliases - may help code readability
Validator.prototype.validate = Validator.prototype.check;
Validator.prototype.assert = Validator.prototype.check;

Validator.prototype.error = function(msg) {
    throw new Error(msg);
    return this;
}

Validator.prototype.isEmail = function() {
    if (!validators.isEmail(this.str)) {
       return this.error(this.msg || 'Invalid email');
    }
    return this;
}

Validator.prototype.isUrl = function() {
    if (!validators.isUrl(this.str)) {
       return this.error(this.msg || 'Invalid URL');
    }
    return this;
}

Validator.prototype.isIP = function() {
    //net.isIP is in node >= 0.3.0
    if (!validators.isIp(this.str)) {
       return this.error(this.msg || 'Invalid IP');
    }
    return this;
}

Validator.prototype.isAlpha = function() {
    if (!validators.isAlpha(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isAlphanumeric = function() {
    if (!validators.isAlphanumeric(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNumeric = function() {
    if (!validators.isNumeric(this.str)) {
       return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isLowercase = function() {
    if (!validators.isLowercase(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isUppercase = function() {
    if (!validators.isUppercase(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isInt = function() {
    if (!validators.isInt(this.str)) {
       return this.error(this.msg || 'Invalid integer');
    }
    return this;
}

Validator.prototype.isDecimal = function() {
    if (!validators.isDecimal(this.str)) {
        return this.error(this.msg || 'Invalid decimal');
    }
    return this;
}

Validator.prototype.isFloat = function() {
    return this.isDecimal();
}

Validator.prototype.isDivisibleBy = function(n) {
    if (!validators.isDivisibleBy(this.str, n)) {
        return this.error(this.msg || 'Not divisible by ' + n);
    }
    return this;
}

Validator.prototype.notNull = function() {
    if (!validators.notNull(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNull = function() {
    if (!validators.isNull(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notEmpty = function() {
    if (this.str.match(/^[\s\t\r\n]*$/)) {
       return this.error(this.msg || 'String is empty');
    }
    return this;
}

Validator.prototype.equals = function(equals) {
    if (this.str != equals) {
       return this.error(this.msg || 'Not equal');
    }
    return this;
}

Validator.prototype.contains = function(str) {
    if (this.str.indexOf(str) === -1) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notContains = function(str) {
    if (this.str.indexOf(str) >= 0) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (! this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.len = function(min, max) {
    if (this.str.length < min) {
       return this.error(this.msg || 'String is too small');
    }
    if (typeof max !== undefined && this.str.length > max) {
       return this.error(this.msg || 'String is too large');
    }
    return this;
}

//Thanks to github.com/sreuter for the idea.
Validator.prototype.isUUID = function(version) {
    if (version == 3 || version == 'v3') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    } else if (version == 4 || version == 'v4') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
    } else {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    }
    if (!this.str.match(pattern)) {
       return this.error(this.msg || 'Not a UUID');
    }
    return this;
}

Validator.prototype.isDate = function() {
    var intDate = Date.parse(this.str);
    if (isNaN(intDate)) {
        return this.error(this.msg || 'Not a date');
    }
    return this;
}

Validator.prototype.isAfter = function(date) {
    date = date || new Date();
    var origDate = toDate(this.str);
    var compDate = toDate(date);

    if (origDate && compDate && origDate < compDate) {
        return this.error(this.msg || 'Invalid date');
    }

    return this;
}

Validator.prototype.isBefore = function(date) {
    date = date || new Date();
    var origDate = toDate(this.str);
    var compDate = toDate(date);

    if (origDate && compDate && origDate > compDate) {
        return this.error(this.msg || 'Invalid date');
    }

    return this;
}

Validator.prototype.isIn = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (!~options.indexOf(this.str)) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid in() argument');
    }
}

Validator.prototype.notIn = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (options.indexOf(this.str) !== -1) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid notIn() argument');
    }
}

Validator.prototype.min = function(val) {
    var number = parseFloat(this.str);

    if (!isNaN(number) && number < val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.max = function(val) {
    var number = parseFloat(this.str);

    if (!isNaN(number) && number > val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isArray = function() {
    if (!Array.isArray(this.str)) {
        return this.error(this.msg || 'Not an array');
    }
    return this;
}

//Will work against Visa, MasterCard, American Express, Discover, Diners Club, and JCB card numbering formats
Validator.prototype.isCreditCard = function() {
    this.str = this.str.replace(/[^0-9]+/g, ''); //remove all dashes, spaces, etc.
    if (!this.str.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/)) {
        return this.error(this.msg || 'Invalid credit card');
    }
    return this;
}

<commit_msg>refactored not empty<commit_after>var net = require('net');

var validators = {
    isEmail: function(str) {
        return str.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
    },
    isUrl: function(str) {
        return str.match(/^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?((?:(?:[-\w\d{1-3}]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|edu|co\.uk|ac\.uk|it|fr|tv|museum|asia|local|travel|[a-z]{2}))|((\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)(\.(\b25[0-5]\b|\b[2][0-4][0-9]\b|\b[0-1]?[0-9]?[0-9]\b)){3}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$ |\/.,*:;=]|%[a-f\d]{2})*)?$/i) || str.length > 2083;
    },
    isIp: function(str) {
        // net.isIp requires node >= 0.3.0
        var modernNode = typeof net.isIP === 'function';
        var method = modernNode? this.isIpNet : this.isIpManual;
        return method(str);
    },
    isIpNet: function(str) {
        return net.isIP(str) !== 0;
    },
    isIpManual: function(str) {
        return str.match(/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/);
    },
    isAlpha: function(str) {
        return str.match(/^[a-zA-Z]+$/);
    },
    isAlphanumeric: function(str) {
        return str.match(/^[a-zA-Z0-9]+$/);
    },
    isNumeric: function(str) {
        return str.match(/^-?[0-9]+$/);
    },
    isLowercase: function(str) {
        return str.match(/^[a-z0-9]+$/);
    },
    isUppercase: function(str) {
        return str.match(/^[A-Z0-9]+$/);
    },
    isInt: function(str) {
        return str.match(/^(?:-?(?:[0-9][0-9]*)(?:\.?0+)?)$/);
    },
    isDecimal: function(str) {
        return str !== '' && str.match(/^(?:-?(?:[0-9]+))?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/);
    },
    isDivisibleBy: function(str, n) {
        return !(parseFloat(str) % n);
    },
    notNull: function(str) {
        return str !== '';
    },
    isNull: function(str) {
        return str === '';
    },
    isEmpty: function(str) {
        return !str.match(/^[\s\t\r\n]*$/);
    }
};

var Validator = exports.Validator = function() {}

Validator.prototype.check = function(str, fail_msg) {
    this.str = (str == null || (isNaN(str) && str.length == undefined)) ? '' : str;
    // Convert numbers to strings but keep arrays/objects
    if (typeof this.str == 'number') {
      this.str += '';
    }
    this.msg = fail_msg;
    this._errors = this._errors || [];
    return this;
}

// Helper function to avoid duplication of code
function toDateTime(date) {
    if (date instanceof Date) {
      return date;
    }
    var intDate = Date.parse(date);
    if (isNaN(intDate)) {
        return null;
    }
    return new Date(intDate);
}

// Convert to date without the time component
function toDate(date) {
    if (!(date instanceof Date)) {
      date = toDateTime(date);
    }
    if (!date) {
      return null;
    }
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    return date;
}

//Create some aliases - may help code readability
Validator.prototype.validate = Validator.prototype.check;
Validator.prototype.assert = Validator.prototype.check;

Validator.prototype.error = function(msg) {
    throw new Error(msg);
    return this;
}

Validator.prototype.isEmail = function() {
    if (!validators.isEmail(this.str)) {
       return this.error(this.msg || 'Invalid email');
    }
    return this;
}

Validator.prototype.isUrl = function() {
    if (!validators.isUrl(this.str)) {
       return this.error(this.msg || 'Invalid URL');
    }
    return this;
}

Validator.prototype.isIP = function() {
    //net.isIP is in node >= 0.3.0
    if (!validators.isIp(this.str)) {
       return this.error(this.msg || 'Invalid IP');
    }
    return this;
}

Validator.prototype.isAlpha = function() {
    if (!validators.isAlpha(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isAlphanumeric = function() {
    if (!validators.isAlphanumeric(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNumeric = function() {
    if (!validators.isNumeric(this.str)) {
       return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isLowercase = function() {
    if (!validators.isLowercase(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isUppercase = function() {
    if (!validators.isUppercase(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isInt = function() {
    if (!validators.isInt(this.str)) {
       return this.error(this.msg || 'Invalid integer');
    }
    return this;
}

Validator.prototype.isDecimal = function() {
    if (!validators.isDecimal(this.str)) {
        return this.error(this.msg || 'Invalid decimal');
    }
    return this;
}

Validator.prototype.isFloat = function() {
    return this.isDecimal();
}

Validator.prototype.isDivisibleBy = function(n) {
    if (!validators.isDivisibleBy(this.str, n)) {
        return this.error(this.msg || 'Not divisible by ' + n);
    }
    return this;
}

Validator.prototype.notNull = function() {
    if (!validators.notNull(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.isNull = function() {
    if (!validators.isNull(this.str)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notEmpty = function() {
    if (!validators.isEmpty(this.str)) {
       return this.error(this.msg || 'String is empty');
    }
    return this;
}

Validator.prototype.equals = function(equals) {
    if (this.str != equals) {
       return this.error(this.msg || 'Not equal');
    }
    return this;
}

Validator.prototype.contains = function(str) {
    if (this.str.indexOf(str) === -1) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notContains = function(str) {
    if (this.str.indexOf(str) >= 0) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.regex = Validator.prototype.is = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (! this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.notRegex = Validator.prototype.not = function(pattern, modifiers) {
    if (typeof pattern !== 'function') {
        pattern = new RegExp(pattern, modifiers);
    }
    if (this.str.match(pattern)) {
       return this.error(this.msg || 'Invalid characters');
    }
    return this;
}

Validator.prototype.len = function(min, max) {
    if (this.str.length < min) {
       return this.error(this.msg || 'String is too small');
    }
    if (typeof max !== undefined && this.str.length > max) {
       return this.error(this.msg || 'String is too large');
    }
    return this;
}

//Thanks to github.com/sreuter for the idea.
Validator.prototype.isUUID = function(version) {
    if (version == 3 || version == 'v3') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    } else if (version == 4 || version == 'v4') {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
    } else {
        pattern = /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
    }
    if (!this.str.match(pattern)) {
       return this.error(this.msg || 'Not a UUID');
    }
    return this;
}

Validator.prototype.isDate = function() {
    var intDate = Date.parse(this.str);
    if (isNaN(intDate)) {
        return this.error(this.msg || 'Not a date');
    }
    return this;
}

Validator.prototype.isAfter = function(date) {
    date = date || new Date();
    var origDate = toDate(this.str);
    var compDate = toDate(date);

    if (origDate && compDate && origDate < compDate) {
        return this.error(this.msg || 'Invalid date');
    }

    return this;
}

Validator.prototype.isBefore = function(date) {
    date = date || new Date();
    var origDate = toDate(this.str);
    var compDate = toDate(date);

    if (origDate && compDate && origDate > compDate) {
        return this.error(this.msg || 'Invalid date');
    }

    return this;
}

Validator.prototype.isIn = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (!~options.indexOf(this.str)) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid in() argument');
    }
}

Validator.prototype.notIn = function(options) {
    if (options && typeof options.indexOf === 'function') {
        if (options.indexOf(this.str) !== -1) {
            return this.error(this.msg || 'Unexpected value');
        }
        return this;
    } else {
        return this.error(this.msg || 'Invalid notIn() argument');
    }
}

Validator.prototype.min = function(val) {
    var number = parseFloat(this.str);

    if (!isNaN(number) && number < val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.max = function(val) {
    var number = parseFloat(this.str);

    if (!isNaN(number) && number > val) {
        return this.error(this.msg || 'Invalid number');
    }
    return this;
}

Validator.prototype.isArray = function() {
    if (!Array.isArray(this.str)) {
        return this.error(this.msg || 'Not an array');
    }
    return this;
}

//Will work against Visa, MasterCard, American Express, Discover, Diners Club, and JCB card numbering formats
Validator.prototype.isCreditCard = function() {
    this.str = this.str.replace(/[^0-9]+/g, ''); //remove all dashes, spaces, etc.
    if (!this.str.match(/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/)) {
        return this.error(this.msg || 'Invalid credit card');
    }
    return this;
}

<|endoftext|>


<commit_before>const _ = require('lodash')
const logging = require('homeautomation-js-lib/logging.js')
const health = require('homeautomation-js-lib/health.js')
const Redis = require('redis')
const utilities = require('./utilities.js')
const metrics = require('homeautomation-js-lib/stats.js')

require('homeautomation-js-lib/redis_helpers.js')

var state = {}
var pendingTopicUpdates = []
var observedTopics = []

// PREFIXES_TO_INGORE=/isy,/test,/homeseer/action,happy,/deconz,/Deconz,/vera,/sonos,/hubitat,/openmqtt,/xiaomi
// REQUIRED_PREFIXES=/
// TOPIC_STRINGS_TO_INGORE=test
// SUFFIXES_TO_INGORE=/set

const requiredPrefixesString = process.env.REQUIRED_PREFIXES
const prefixesToIgnoreString = process.env.PREFIXES_TO_IGNORE
const topicStringsToIgnoreString = process.env.TOPIC_STRINGS_TO_INGORE
const stuffixesToIgnoreString = process.env.SUFFIXES_TO_IGNORE

const requiredPrefixes = _.isNil(requiredPrefixesString) ? null : requiredPrefixesString.split(',')
const prefixesToIgnore = _.isNil(prefixesToIgnoreString) ? null : prefixesToIgnoreString.split(',')
const topicStringsToIgnore = _.isNil(topicStringsToIgnoreString) ? null : topicStringsToIgnoreString.split(',')
const stuffixesToIgnore = _.isNil(stuffixesToIgnoreString) ? null : stuffixesToIgnoreString.split(',')

const isInterestingDevice = function (deviceTopic) {
    if (_.isNil(deviceTopic)) {
        return false
    }

    if (deviceTopic.length == 1) {
        return false
    }

    var match = false

    if (!_.isNil(requiredPrefixes)) {
        requiredPrefixes.forEach(prefix => {
            match |= deviceTopic.startsWith(prefix)
        })

        if (!match) return false
    }

    if (!_.isNil(topicStringsToIgnore)) {
        topicStringsToIgnore.forEach(string => {
            match |= deviceTopic.includes(string)
        })

        if (match) return false
    }

    if (!_.isNil(prefixesToIgnore)) {
        prefixesToIgnore.forEach(prefix => {
            match |= deviceTopic.startsWith(prefix)
        })

        if (match) return false
    }

    if (!_.isNil(stuffixesToIgnore)) {
        stuffixesToIgnore.forEach(suffix => {
            match |= deviceTopic.endsWith(suffix)
        })

        if (match) return false
    }

    return true
}

const shouldUseRedis = function () {
    logging.debug('Redis supported: ' + (!global.isTestMode() && !_.isNil(process.env.REDIS_HOST)))

    return !global.isTestMode() && !_.isNil(process.env.REDIS_HOST)
}

var redis = null

// Config
const expireAfterMinutes = process.env.EXPIRE_KEYS_AFTER_MINUTES

const connectToRedis = function (callback) {
    if (!shouldUseRedis()) {
        if (!_.isNil(callback)) {
            callback()
        }

        return
    }

    if (!_.isNil(redis)) {
        if (!_.isNil(callback)) {
            callback()
        }

        return
    }

    logging.debug(' setting up redis client')
    redis = Redis.setupClient(function () {
        logging.info('redis connected ', {
            action: 'redis-connected'
        })

        if (!_.isNil(callback)) {
            callback()
            callback = null
        }

    }, function () {
        logging.error('redis disconnected ', {
            action: 'redis-disconnected'
        })
    })
}

const secondsToDefer = 10
var delayedUpdate = null

const processPendingUpdates = function () {
    if (!shouldUseRedis()) {
        return
    }

    if (!_.isNil(delayedUpdate)) {
        clearTimeout(delayedUpdate)
        delayedUpdate = null
    }

    var timeToExpire = expireAfterMinutes

    logging.debug('processing updates for topics: ' + pendingTopicUpdates)
    pendingTopicUpdates.forEach(topic => {
        if (!isInterestingDevice(topic)) {
            redis.set(topic, null)
            return
        }

        const message = state[topic]

        if (_.isNil(timeToExpire)) {
            redis.set(topic, message)
        } else {
            redis.set(topic, message, 'EX', (timeToExpire * 60)) // redis takes seconds
        }
    });

    pendingTopicUpdates = []
    logging.debug(' => done')

    // Prune un-needed values
    logging.debug(' Pruning state')
    Object.keys(state).forEach(topic => {
        if (!observedTopics.includes(topic)) {
            logging.info('No longer need to observe, pruning:' + topic)
            delete(state[topic])
        }
    });

    logging.debug(' => done')
}

const setPendingUpdateForTopic = function (topic) {
    logging.debug('setPendingUpdateForTopic: ' + topic)

    if (pendingTopicUpdates.includes(topic)) {
        return
    }

    pendingTopicUpdates.push(topic)

    if ( _.isNil(delayedUpdate)) {
        delayedUpdate = _.delay(processPendingUpdates, secondsToDefer * 1000)
    }
}

var initialStateLoaded = false

const _loadInitialState = function (topics, callback) {
    if (initialStateLoaded) {
        return
    }

    const completion = function () {
        logging.debug(' initial state loaded, sending callback: ' + JSON.stringify(state))

        initialStateLoaded = true
        if (!_.isNil(callback)) {
            callback(true)
        }
    }

    if (!global.isTestMode()) {
        logging.debug(' starting connection')
        connectToRedis(function () {
            logging.debug(' connection completed')

            _updateObservedTopics(topics, function () {
                logging.debug(' done updating observed topics')
                completion()
            })
        })
    } else {
        completion()
    }
}

module.exports.isInitialStateLoaded = function () {
    return initialStateLoaded
}

const _updateObservedTopics = function (topics, callback) {
    observedTopics = topics

    if (!shouldUseRedis()) {
        logging.debug('will not update obseved topics, redis not used')
        if (!_.isNil(callback)) {
            callback()
        }
        return
    }

    if (!redis.connected) {
        logging.error('Cannot update topics when redis is not connected')
        if (!_.isNil(callback)) {
            callback()
        }
        return
    }

    logging.debug('updating observed topics: ' + topics)

    const redisStartTime = new Date().getTime()
    logging.debug(' redis query', {
        action: 'redis-query-start',
        start_time: redisStartTime
    })

    redis.mget(topics, function (err, values) {
        const redisQueryTime = ((new Date().getTime()) - redisStartTime)
        logging.debug(' redis query done', {
            action: 'redis-query-done',
            query_time: redisQueryTime
        })

        metrics.submit('redis_query_time', redisQueryTime)

        var context = {}

        for (var index = 0; index < topics.length; index++) {
            const key = topics[index]
            const value = values[index]

            // Only update state if it isn't present
            if (_.isNil(state[key])) {
                state[key] = utilities.convertToNumberIfNeeded(value)
            }
        }

        try {
            var jsonFound = JSON.parse(message)
            if (!_.isNil(jsonFound)) {
                Object.keys(jsonFound).forEach(function (key) {
                    context[key] = jsonFound[key]
                })
            }
        } catch (err) {
            logging.debug('invalid json')
        }

        if (!_.isNil(callback)) {
            callback()
        }
    })
}

module.exports.updateObservedTopics = function (topics, callback) {
    if (initialStateLoaded) {
        _updateObservedTopics(topics, callback)
    } else {
        _loadInitialState(topics, callback)
    }
}

module.exports.clearState = function () {
    state = {}
    pendingTopicUpdates = []
    observedTopics = []
}

module.exports.update = function (topic, message) {
    if (_.isNil(topic) || _.isNil(message)) {
        return
    }

    state[topic] = utilities.convertToNumberIfNeeded(message)
    health.healthyEvent()

    if (shouldUseRedis()) {
        setPendingUpdateForTopic(topic)
    }
}

module.exports.valueForTopic = function (topic) {
    if (_.isNil(topic)) {
        return nil
    }

    logging.debug(' returning topic: ' + topic + ' value: ' + state[topic])

    return state[topic]
}

module.exports.valuesForTopics = function (topics) {
    if (_.isNil(topics)) {
        return nil
    }

    var result = {}

    topics.forEach(topic => {
        result[topic] = state[topic]
    });

    logging.debug(' returning values for topics: ' + JSON.stringify(result))

    return result
}<commit_msg>Updating<commit_after>const _ = require('lodash')
const logging = require('homeautomation-js-lib/logging.js')
const health = require('homeautomation-js-lib/health.js')
const Redis = require('redis')
const utilities = require('./utilities.js')
const metrics = require('homeautomation-js-lib/stats.js')

require('homeautomation-js-lib/redis_helpers.js')

var state = {}
var pendingTopicUpdates = []
var observedTopics = []

// PREFIXES_TO_INGORE=/isy,/test,/homeseer/action,happy,/deconz,/Deconz,/vera,/sonos,/hubitat,/openmqtt,/xiaomi
// REQUIRED_PREFIXES=/
// TOPIC_STRINGS_TO_INGORE=test
// SUFFIXES_TO_INGORE=/set

const requiredPrefixesString = process.env.REQUIRED_PREFIXES
const prefixesToIgnoreString = process.env.PREFIXES_TO_IGNORE
const topicStringsToIgnoreString = process.env.TOPIC_STRINGS_TO_INGORE
const stuffixesToIgnoreString = process.env.SUFFIXES_TO_IGNORE

const requiredPrefixes = _.isNil(requiredPrefixesString) ? null : requiredPrefixesString.split(',')
const prefixesToIgnore = _.isNil(prefixesToIgnoreString) ? null : prefixesToIgnoreString.split(',')
const topicStringsToIgnore = _.isNil(topicStringsToIgnoreString) ? null : topicStringsToIgnoreString.split(',')
const stuffixesToIgnore = _.isNil(stuffixesToIgnoreString) ? null : stuffixesToIgnoreString.split(',')

const isInterestingDevice = function(deviceTopic) {
	if (_.isNil(deviceTopic)) {
		return false
	}

	if (deviceTopic.length == 1) {
		return false
	}

	var match = false

	if (!_.isNil(requiredPrefixes)) {
		requiredPrefixes.forEach(prefix => {
			match |= deviceTopic.startsWith(prefix)
		})

		if (!match) {
			return false
		}
	}

	if (!_.isNil(topicStringsToIgnore)) {
		topicStringsToIgnore.forEach(string => {
			match |= deviceTopic.includes(string)
		})

		if (match) {
			return false
		}
	}

	if (!_.isNil(prefixesToIgnore)) {
		prefixesToIgnore.forEach(prefix => {
			match |= deviceTopic.startsWith(prefix)
		})

		if (match) {
			return false
		}
	}

	if (!_.isNil(stuffixesToIgnore)) {
		stuffixesToIgnore.forEach(suffix => {
			match |= deviceTopic.endsWith(suffix)
		})

		if (match) {
			return false
		}
	}

	return true
}

const shouldUseRedis = function() {
	logging.debug('Redis supported: ' + (!global.isTestMode() && !_.isNil(process.env.REDIS_HOST)))

	return !global.isTestMode() && !_.isNil(process.env.REDIS_HOST)
}

var redis = null

// Config
const expireAfterMinutes = process.env.EXPIRE_KEYS_AFTER_MINUTES

const connectToRedis = function(callback) {
	if (!shouldUseRedis()) {
		if (!_.isNil(callback)) {
			callback()
		}

		return
	}

	if (!_.isNil(redis)) {
		if (!_.isNil(callback)) {
			callback()
		}

		return
	}

	logging.debug(' setting up redis client')
	redis = Redis.setupClient(function() {
		logging.info('redis connected ', {
			action: 'redis-connected'
		})

		if (!_.isNil(callback)) {
			callback()
			callback = null
		}

	}, function() {
		logging.error('redis disconnected ', {
			action: 'redis-disconnected'
		})
	})
}

const secondsToDefer = 10
var delayedUpdate = null

const processPendingUpdates = function() {
	if (!shouldUseRedis()) {
		return
	}

	if (!_.isNil(delayedUpdate)) {
		clearTimeout(delayedUpdate)
		delayedUpdate = null
	}

	var timeToExpire = expireAfterMinutes

	logging.debug('processing updates for topics: ' + pendingTopicUpdates)
	pendingTopicUpdates.forEach(topic => {
		if (!isInterestingDevice(topic)) {
			redis.set(topic, null)
			return
		}

		const message = state[topic]

		if (_.isNil(timeToExpire)) {
			redis.set(topic, message)
		} else {
			redis.set(topic, message, 'EX', (timeToExpire * 60)) // redis takes seconds
		}
	})

	pendingTopicUpdates = []
	logging.debug(' => done')

	// Prune un-needed values
	logging.debug(' Pruning state')
	Object.keys(state).forEach(topic => {
		if (!observedTopics.includes(topic)) {
			logging.info('No longer need to observe, pruning:' + topic)
			delete(state[topic])
		}
	})

	logging.debug(' => done')
}

const setPendingUpdateForTopic = function(topic) {
	logging.debug('setPendingUpdateForTopic: ' + topic)

	if (pendingTopicUpdates.includes(topic)) {
		return
	}

	pendingTopicUpdates.push(topic)

	if ( _.isNil(delayedUpdate)) {
		delayedUpdate = _.delay(processPendingUpdates, secondsToDefer * 1000)
	}
}

var initialStateLoaded = false

const _loadInitialState = function(topics, callback) {
	if (initialStateLoaded) {
		return
	}

	const completion = function() {
		logging.debug(' initial state loaded, sending callback: ' + JSON.stringify(state))

		initialStateLoaded = true
		if (!_.isNil(callback)) {
			callback(true)
		}
	}

	if (!global.isTestMode()) {
		logging.debug(' starting connection')
		connectToRedis(function() {
			logging.debug(' connection completed')

			_updateObservedTopics(topics, function() {
				logging.debug(' done updating observed topics')
				completion()
			})
		})
	} else {
		completion()
	}
}

module.exports.isInitialStateLoaded = function() {
	return initialStateLoaded
}

const _updateObservedTopics = function(topics, callback) {
	observedTopics = topics

	if (!shouldUseRedis()) {
		logging.debug('will not update obseved topics, redis not used')
		if (!_.isNil(callback)) {
			callback()
		}
		return
	}

	if (!redis.connected) {
		logging.error('Cannot update topics when redis is not connected')
		if (!_.isNil(callback)) {
			callback()
		}
		return
	}

	logging.debug('updating observed topics: ' + topics)

	const redisStartTime = new Date().getTime()
	logging.debug(' redis query', {
		action: 'redis-query-start',
		start_time: redisStartTime
	})

	redis.mget(topics, function(err, values) {
		const redisQueryTime = ((new Date().getTime()) - redisStartTime)
		logging.debug(' redis query done', {
			action: 'redis-query-done',
			query_time: redisQueryTime
		})

		metrics.submit('redis_query_time', redisQueryTime)

		var context = {}

		for (var index = 0; index < topics.length; index++) {
			const key = topics[index]
			const value = values[index]

			// Only update state if it isn't present
			if (_.isNil(state[key])) {
				state[key] = utilities.convertToNumberIfNeeded(value)
			}
		}

		try {
			var jsonFound = JSON.parse(message)
			if (!_.isNil(jsonFound)) {
				Object.keys(jsonFound).forEach(function(key) {
					context[key] = jsonFound[key]
				})
			}
		} catch (err) {
			logging.debug('invalid json')
		}

		if (!_.isNil(callback)) {
			callback()
		}
	})
}

module.exports.updateObservedTopics = function(topics, callback) {
	if (initialStateLoaded) {
		_updateObservedTopics(topics, callback)
	} else {
		_loadInitialState(topics, callback)
	}
}

module.exports.clearState = function() {
	state = {}
	pendingTopicUpdates = []
	observedTopics = []
}

module.exports.update = function(topic, message) {
	if (_.isNil(topic) || _.isNil(message)) {
		return
	}

	state[topic] = utilities.convertToNumberIfNeeded(message)
	health.healthyEvent()

	if (shouldUseRedis()) {
		setPendingUpdateForTopic(topic)
	}
}

module.exports.valueForTopic = function(topic) {
	if (_.isNil(topic)) {
		return nil
	}

	logging.debug(' returning topic: ' + topic + ' value: ' + state[topic])

	return state[topic]
}

module.exports.valuesForTopics = function(topics) {
	if (_.isNil(topics)) {
		return nil
	}

	var result = {}

	topics.forEach(topic => {
		result[topic] = state[topic]
	})

	logging.debug(' returning values for topics: ' + JSON.stringify(result))

	return result
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

require('./proof')(2, function (Strata, tmp, serialize, equal, ok, step) {
    var fs = require ('fs'), strata
    step(function () {
        serialize(__dirname + '/fixtures/merge.before.json', tmp, step())
    }, function () {
        strata = new Strata({ directory: tmp, leafSize: 3, branchSize: 3 })
        strata.open(step())
    }, function () {
        strata.mutator(strata.right, step())
    }, function (cursor) {
        ok(cursor.exclusive, 'exclusive')
        step(function () {
            cursor.get(0, step())
        }, function (got) {
            equal(got, 'c', 'right')
            cursor.unlock()
        })
    }, function () {
        strata.close(step())
    })
})
<commit_msg>Tidy `t/basics/right.t.js`.<commit_after>#!/usr/bin/env node

require('./proof')(2, function (step, Strata, tmp, serialize, equal, ok) {
    var strata
    step(function () {
        serialize(__dirname + '/fixtures/merge.before.json', tmp, step())
    }, function () {
        strata = new Strata({ directory: tmp, leafSize: 3, branchSize: 3 })
        strata.open(step())
    }, function () {
        strata.mutator(strata.right, step())
    }, function (cursor) {
        ok(cursor.exclusive, 'exclusive')
        step(function () {
            cursor.get(0, step())
        }, function (got) {
            equal(got, 'c', 'right')
            cursor.unlock()
        })
    }, function () {
        strata.close(step())
    })
})
<|endoftext|>


<commit_before>/**
 * Client side assets management module for both balancer and service.
 */

/**
 * Module dependencies
 */

var fs = require('fs')
var vm = require('vm')
var path = require('path')
var jspm = require('jspm')
var debug = require('debug')('micromono:web:asset')
var assign = require('lodash.assign')
var isarray = require('lodash.isarray')
var child_process = require('child_process')

/**
 * MicroMono Asset class constructor
 * @param  {String} [packagePath] Path to the package where package.json is located.
 */
var Asset = module.exports = function MicroMonoAsset(packagePath) {
  if ('string' === typeof packagePath) {
    this.packagePath = packagePath
    // require('jspm/lib/config').loaded = false
    jspm.setPackagePath(packagePath)
  } else if ('object' === typeof packagePath) {
    this.jspmInfo = packagePath
  }
}

/**
 * MicroMono Asset public API.
 */
Asset.prototype = {

  /**
   * Find and parse jspm information from package.json
   *
   * @param  {String} [packagePath] Path to the package where package.json is located.
   * @return {Object}  Information of JSPM
   */
  parseJSPM: function(packagePath) {
    packagePath = packagePath || this.packagePath
    var pjsonPath = path.join(packagePath, 'package.json')
    var pjson = require(pjsonPath)
    this.pkgInfo = pjson

    if (pjson.jspm) {
      var jspmInfo = pjson.jspm
      var micromonoInfo = pjson.micromono || {}
      this.jspmInfo = jspmInfo
      if (!jspmInfo.name) {
        // Assign the package name to jspm object
        jspmInfo.name = micromonoInfo.name || pjson.name
      }
      var directories = jspmInfo.directories || {}

      // base url for generating other urls (e.g. config.js, system.js etc.)
      if (!micromonoInfo.publicURL) {
        micromonoInfo.publicURL = path.join(directories.baseURL || '/', pjson.name)
      }
      var publicURL = micromonoInfo.publicURL || directories.baseURL || '/'
      var publicPath = directories.baseURL || '/'

      if (!isarray(publicURL)) {
        publicURL = [publicURL]
      }

      publicURL = publicURL.map(function(url) {
        if (url[0] !== '/') {
          url = '/' + url
        }
        return url
      })

      this.publicURL = publicURL
      jspmInfo.publicURL = publicURL
      this.publicPath = path.join(packagePath, publicPath)

      jspmInfo.main = jspmInfo.main || 'index.js'
      // relative urls of assets
      jspmInfo.urls = {
        configJs: path.join(publicURL[0], '/config.js'),
        systemJs: path.join(publicURL[0], '/jspm_packages/system.js'),
        entryJs: path.join(publicURL[0], jspmInfo.main + (/\.js$/.test(jspmInfo.main) ? '' : '.js'))
      }

      return jspmInfo
    }
  },

  getPublicURL: function() {
    return this.jspmInfo && this.jspmInfo.publicURL
  },

  getPublicPath: function() {
    return this.publicPath
  },

  mergeJSPMDeps: function(jspmInfo) {
    if (!jspmInfo || !jspmInfo.dependencies) {
      return
    }

    debug('merging jspm dependencies', jspmInfo.dependencies)

    var _deps
    var self = this
    var depsToMerge = jspmInfo.dependencies

    if (depsToMerge && Object.keys(depsToMerge).length > 0 && !this.jspmInfo) {
      this.jspmInfo = {
        directories: {
          baseURL: 'public'
        },
        dependencies: {}
      }
      this.needConfigJSPM = true
    }

    _deps = this.jspmInfo.dependencies || {}

    Object.keys(depsToMerge).forEach(function(depName) {
      var oldDep = _deps[depName]
      var dep = depsToMerge[depName]
      if (!oldDep) {
        _deps[depName] = dep
        self.needConfigJSPM = true
      } else if (oldDep !== dep) {
        debug('You have potential conflicting dependencies: %s vs %s', oldDep, dep)
      }
    })

    if (Object.keys(_deps).length > 0) {
      this.jspmInfo.dependencies = _deps
    }

    debug('need to configure jspm: %s', this.needConfigJSPM ? 'yes' : 'no')
  },

  configJSPM: function() {
    if (!this.pkgInfo || !this.needConfigJSPM) {
      return Promise.resolve()
    }

    debug('configuring jspm')

    var jspmInfo = this.jspmInfo
    var _jspmInfo = this.pkgInfo.jspm || {}
    _jspmInfo.directories = assign(jspmInfo.directories, _jspmInfo.directories)
    _jspmInfo.dependencies = assign(jspmInfo.dependencies, _jspmInfo.dependencies)
    this.pkgInfo.jspm = _jspmInfo

    var pkgInfoStr = JSON.stringify(this.pkgInfo, null, 2)
    fs.writeFileSync(path.join(this.packagePath, 'package.json'), pkgInfoStr)
    this.parseJSPM()

    if (this.packagePath) {
      debug('run `jspm install` in path %s', this.packagePath)
      var childOpts = {
        stdio: [process.stdin, process.stdout, process.stderr]
      }

      var jspmBinPath = path.join(this.packagePath, '/node_modules/.bin/jspm')
      var child = child_process.spawn(jspmBinPath, ['install'], childOpts)

      return new Promise(function(resolve, reject) {
        child.on('exit', resolve).on('error', reject)
      })
    } else {
      debug('asset has no `packagePath`, skip jspm install.')
      return Promise.resolve()
    }
  },

  /**
   * Bundle app code
   *
   * @param  {Object}   options for bundling assets
   * Default value for options:
   *         {
   *           bundleDeps: false,
   *           outFile: 'bundle' + ['-' + package name] + '.js',
   *           sourceMaps: 'inline',
   *           lowResSourceMaps: true,
   *           inject: false,
   *           minify: false,
   *           mangle: true,
   *           buildCss: true,
   *           separateCss: false
   *         }
   *
   * Default options when NODE_ENV is `production`:
   *         {
   *           bundleDeps: false,
   *           outFile: 'bundle' + ['-' + package name] + '.js',
   *           sourceMaps: false,
   *           sourceMapContents: false,
   *           lowResSourceMaps: true,
   *           inject: true,
   *           minify: true,
   *           mangle: true,
   *           buildCss: true,
   *           separateCss: true
   *         }
   *
   * @param {String} [env] A pre-set default options. Now only support `production`.
   * @return {Promise}
   */
  bundle: function(options, env) {
    var self = this
    var jspmInfo = this.jspmInfo || this.parseJSPM(this.packagePath)

    if (!jspmInfo) {
      // Don't build bundle if there's no jspm info found
      return Promise.resolve()
    }

    options = options || {}
    var bundleCmd = 'bundle ' + path.join(this.getPublicPath(), jspmInfo.main)
    // bundleCmd = jspmInfo.main

    // ignore or include dependencies
    var operator = options.bundleDeps === true ? ' + ' : ' - '
    var deps = jspmInfo.dependencies
    deps = Object.keys(deps)
    if (deps.length > 0) {
      var ignoreDeps = operator + deps.join(operator)
      bundleCmd += ignoreDeps
    }

    // Prepare options for jspm/systemjs builder
    options.name = jspmInfo.name
    options = getDefaultBundleOptions(options, env)
    var outFile = options.outFile

    if ('/' !== outFile[0]) {
      outFile = path.join(self.publicPath, outFile)
    }

    // add entries info
    var publicURL = Array.isArray(this.publicURL) ? this.publicURL[0] : this.publicURL

    // override to make sure systemjs use the correct `outFile` path
    options.outFile = outFile

    bundleCmd += convertBundleOptionsToStr(options)

    // Set options for systemjs loader
    writeConfigSync(this.publicPath, options)

    debug('jspm %s\n%s\nwith options:\n%s',
      bundleCmd, outFile, JSON.stringify(options, null, 2))

    var jspmBinPath = path.join(this.packagePath, '/node_modules/.bin/jspm')
    var child = child_process.spawn(jspmBinPath, bundleCmd.split(' '))

    child.stderr.on('data', function(chunk) {
      debug(chunk.toString())
    })
    child.stdout.on('data', function(chunk) {
      debug(chunk.toString())
    })

    return new Promise(function(resolve, reject) {
      child.on('exit', resolve).on('error', reject)
    }).then(function() {
      try {
        if (fs.statSync(outFile).isFile()) {
          debug('jspm js file bundled at: %s', outFile)
          jspmInfo.urls.bundleJs = path.join(publicURL, path.basename(outFile))
        }
      } catch (e) {
        debug('No js file bundled at: %s', outFile)
      }
      try {
        var cssOutFile = outFile.replace(/\.js$/, '.css')
        if (fs.statSync(cssOutFile).isFile()) {
          debug('jspm css file bundled at: %s', cssOutFile)
          jspmInfo.urls.bundleCss = path.join(publicURL,
            path.basename(outFile.replace(/\.js$/, '.css')))
        }
      } catch (e) {
        debug('No css file bundled at: %s', cssOutFile)
      }
    }).catch(function(e) {
      debug('asset bundle error', e.stack || e.message);
    })
  }
}

/**
 * MicroMono Asset private functions.
 */

function writeConfigSync(publicPath, options) {
  var configPath = path.join(publicPath, 'config.js')
  var configCode = fs.readFileSync(configPath)

  var sandbox = {
    System: {
      config: function(cfg) {
        return cfg
      }
    },
    oldConfig: null
  }

  var script = new vm.Script('oldConfig = ' + configCode)
  script.runInNewContext(sandbox)

  var oldConfig = sandbox.oldConfig
  if (oldConfig) {
    // Note: the systemjs loader need `CSS` upper case.
    oldConfig.buildCSS = options.buildCss || false
    oldConfig.separateCSS = options.separateCss || false
    debug('update jspm config file: %s', configPath)
    fs.writeFileSync(configPath, 'System.config(' + JSON.stringify(oldConfig, null, 2) + ');')
  }
}

function getDefaultBundleOptions(opts, env) {
  opts = opts || {}
  var _opts = {
    bundleDeps: false,
    outFile: 'bundle' + (opts.name ? '-' + opts.name : '') + '.js',
    sourceMaps: 'inline',
    sourceMapContents: true,
    lowResSourceMaps: true,
    inject: false,
    minify: false,
    mangle: false,
    buildCss: true,
    separateCss: false
  }

  env = env || process.env.NODE_ENV
  // Set default options for production.
  if ('production' === env) {
    _opts.sourceMaps = false
    _opts.sourceMapContents = false
    _opts.lowResSourceMaps = false
    _opts.inject = true
    _opts.minify = true
    _opts.mangle = true
    _opts.buildCss = true
    _opts.separateCss = true
  }

  _opts = assign(_opts, opts)
  // make sure we have the `.js` suffix for outFile
  var ext = path.extname(_opts.outFile)
  if (!ext) {
    _opts.outFile += '.js'
  } else if ('.js' !== ext) {
    // Replace it with the .js suffix. e.g. `.jsx`.
    _opts.outFile = _opts.outFile.slice(0, -ext.length) + '.js'
  }
  return _opts
}

function convertBundleOptionsToStr(options) {
  var str = ' ' + options.outFile

  if (!options.sourceMaps) {
    str += ' --skip-source-maps'
  }
  if (options.sourceMapContents) {
    str += ' --source-map-contents'
  }
  if (options.inject) {
    str += ' --inject'
  }
  if (options.minify) {
    str += ' --minify'
  }
  if (!options.mangle) {
    str += ' --no-mangle'
  }

  return str
}
<commit_msg>Set cwd for child process.<commit_after>/**
 * Client side assets management module for both balancer and service.
 */

/**
 * Module dependencies
 */

var fs = require('fs')
var vm = require('vm')
var path = require('path')
var jspm = require('jspm')
var debug = require('debug')('micromono:web:asset')
var assign = require('lodash.assign')
var isarray = require('lodash.isarray')
var child_process = require('child_process')

/**
 * MicroMono Asset class constructor
 * @param  {String} [packagePath] Path to the package where package.json is located.
 */
var Asset = module.exports = function MicroMonoAsset(packagePath) {
  if ('string' === typeof packagePath) {
    this.packagePath = packagePath
    // require('jspm/lib/config').loaded = false
    jspm.setPackagePath(packagePath)
  } else if ('object' === typeof packagePath) {
    this.jspmInfo = packagePath
  }
}

/**
 * MicroMono Asset public API.
 */
Asset.prototype = {

  /**
   * Find and parse jspm information from package.json
   *
   * @param  {String} [packagePath] Path to the package where package.json is located.
   * @return {Object}  Information of JSPM
   */
  parseJSPM: function(packagePath) {
    packagePath = packagePath || this.packagePath
    var pjsonPath = path.join(packagePath, 'package.json')
    var pjson = require(pjsonPath)
    this.pkgInfo = pjson

    if (pjson.jspm) {
      var jspmInfo = pjson.jspm
      var micromonoInfo = pjson.micromono || {}
      this.jspmInfo = jspmInfo
      if (!jspmInfo.name) {
        // Assign the package name to jspm object
        jspmInfo.name = micromonoInfo.name || pjson.name
      }
      var directories = jspmInfo.directories || {}

      // base url for generating other urls (e.g. config.js, system.js etc.)
      if (!micromonoInfo.publicURL) {
        micromonoInfo.publicURL = path.join(directories.baseURL || '/', pjson.name)
      }
      var publicURL = micromonoInfo.publicURL || directories.baseURL || '/'
      var publicPath = directories.baseURL || '/'

      if (!isarray(publicURL)) {
        publicURL = [publicURL]
      }

      publicURL = publicURL.map(function(url) {
        if (url[0] !== '/') {
          url = '/' + url
        }
        return url
      })

      this.publicURL = publicURL
      jspmInfo.publicURL = publicURL
      this.publicPath = path.join(packagePath, publicPath)

      jspmInfo.main = jspmInfo.main || 'index.js'
      // relative urls of assets
      jspmInfo.urls = {
        configJs: path.join(publicURL[0], '/config.js'),
        systemJs: path.join(publicURL[0], '/jspm_packages/system.js'),
        entryJs: path.join(publicURL[0], jspmInfo.main + (/\.js$/.test(jspmInfo.main) ? '' : '.js'))
      }

      return jspmInfo
    }
  },

  getPublicURL: function() {
    return this.jspmInfo && this.jspmInfo.publicURL
  },

  getPublicPath: function() {
    return this.publicPath
  },

  mergeJSPMDeps: function(jspmInfo) {
    if (!jspmInfo || !jspmInfo.dependencies) {
      return
    }

    debug('merging jspm dependencies', jspmInfo.dependencies)

    var _deps
    var self = this
    var depsToMerge = jspmInfo.dependencies

    if (depsToMerge && Object.keys(depsToMerge).length > 0 && !this.jspmInfo) {
      this.jspmInfo = {
        directories: {
          baseURL: 'public'
        },
        dependencies: {}
      }
      this.needConfigJSPM = true
    }

    _deps = this.jspmInfo.dependencies || {}

    Object.keys(depsToMerge).forEach(function(depName) {
      var oldDep = _deps[depName]
      var dep = depsToMerge[depName]
      if (!oldDep) {
        _deps[depName] = dep
        self.needConfigJSPM = true
      } else if (oldDep !== dep) {
        debug('You have potential conflicting dependencies: %s vs %s', oldDep, dep)
      }
    })

    if (Object.keys(_deps).length > 0) {
      this.jspmInfo.dependencies = _deps
    }

    debug('need to configure jspm: %s', this.needConfigJSPM ? 'yes' : 'no')
  },

  configJSPM: function() {
    if (!this.pkgInfo || !this.needConfigJSPM) {
      return Promise.resolve()
    }

    debug('configuring jspm')

    var jspmInfo = this.jspmInfo
    var _jspmInfo = this.pkgInfo.jspm || {}
    _jspmInfo.directories = assign(jspmInfo.directories, _jspmInfo.directories)
    _jspmInfo.dependencies = assign(jspmInfo.dependencies, _jspmInfo.dependencies)
    this.pkgInfo.jspm = _jspmInfo

    var pkgInfoStr = JSON.stringify(this.pkgInfo, null, 2)
    fs.writeFileSync(path.join(this.packagePath, 'package.json'), pkgInfoStr)
    this.parseJSPM()

    if (this.packagePath) {
      debug('run `jspm install` in path %s', this.packagePath)
      var childOpts = {
        stdio: [process.stdin, process.stdout, process.stderr]
      }

      var jspmBinPath = path.join(this.packagePath, '/node_modules/.bin/jspm')
      var child = child_process.spawn(jspmBinPath, ['install'], childOpts)

      return new Promise(function(resolve, reject) {
        child.on('exit', resolve).on('error', reject)
      })
    } else {
      debug('asset has no `packagePath`, skip jspm install.')
      return Promise.resolve()
    }
  },

  /**
   * Bundle app code
   *
   * @param  {Object}   options for bundling assets
   * Default value for options:
   *         {
   *           bundleDeps: false,
   *           outFile: 'bundle' + ['-' + package name] + '.js',
   *           sourceMaps: 'inline',
   *           lowResSourceMaps: true,
   *           inject: false,
   *           minify: false,
   *           mangle: true,
   *           buildCss: true,
   *           separateCss: false
   *         }
   *
   * Default options when NODE_ENV is `production`:
   *         {
   *           bundleDeps: false,
   *           outFile: 'bundle' + ['-' + package name] + '.js',
   *           sourceMaps: false,
   *           sourceMapContents: false,
   *           lowResSourceMaps: true,
   *           inject: true,
   *           minify: true,
   *           mangle: true,
   *           buildCss: true,
   *           separateCss: true
   *         }
   *
   * @param {String} [env] A pre-set default options. Now only support `production`.
   * @return {Promise}
   */
  bundle: function(options, env) {
    var self = this
    var jspmInfo = this.jspmInfo || this.parseJSPM(this.packagePath)

    if (!jspmInfo) {
      // Don't build bundle if there's no jspm info found
      return Promise.resolve()
    }

    options = options || {}
    var bundleCmd = 'bundle ' + jspmInfo.main
    // bundleCmd = jspmInfo.main

    // ignore or include dependencies
    var operator = options.bundleDeps === true ? ' + ' : ' - '
    var deps = jspmInfo.dependencies
    deps = Object.keys(deps)
    if (deps.length > 0) {
      var ignoreDeps = operator + deps.join(operator)
      bundleCmd += ignoreDeps
    }

    // Prepare options for jspm/systemjs builder
    options.name = jspmInfo.name
    options = getDefaultBundleOptions(options, env)
    var outFile = options.outFile

    if ('/' !== outFile[0]) {
      outFile = path.join(self.publicPath, outFile)
    }

    // add entries info
    var publicURL = Array.isArray(this.publicURL) ? this.publicURL[0] : this.publicURL

    // override to make sure systemjs use the correct `outFile` path
    options.outFile = outFile

    bundleCmd += convertBundleOptionsToStr(options)

    // Set options for systemjs loader
    writeConfigSync(this.publicPath, options)

    debug('jspm %s\n%s\nwith options:\n%s',
      bundleCmd, outFile, JSON.stringify(options, null, 2))

    var jspmBinPath = path.join(this.packagePath, '/node_modules/.bin/jspm')
    var child = child_process.spawn(jspmBinPath, bundleCmd.split(' '), {
      cwd: this.packagePath
    })

    child.stderr.on('data', function(chunk) {
      debug(chunk.toString())
    })
    child.stdout.on('data', function(chunk) {
      debug(chunk.toString())
    })

    return new Promise(function(resolve, reject) {
      child.on('exit', resolve).on('error', reject)
    }).then(function() {
      try {
        if (fs.statSync(outFile).isFile()) {
          debug('jspm js file bundled at: %s', outFile)
          jspmInfo.urls.bundleJs = path.join(publicURL, path.basename(outFile))
        }
      } catch (e) {
        debug('No js file bundled at: %s', outFile)
      }
      try {
        var cssOutFile = outFile.replace(/\.js$/, '.css')
        if (fs.statSync(cssOutFile).isFile()) {
          debug('jspm css file bundled at: %s', cssOutFile)
          jspmInfo.urls.bundleCss = path.join(publicURL,
            path.basename(outFile.replace(/\.js$/, '.css')))
        }
      } catch (e) {
        debug('No css file bundled at: %s', cssOutFile)
      }
    }).catch(function(e) {
      debug('asset bundle error', e.stack || e.message);
    })
  }
}

/**
 * MicroMono Asset private functions.
 */

function writeConfigSync(publicPath, options) {
  var configPath = path.join(publicPath, 'config.js')
  var configCode = fs.readFileSync(configPath)

  var sandbox = {
    System: {
      config: function(cfg) {
        return cfg
      }
    },
    oldConfig: null
  }

  var script = new vm.Script('oldConfig = ' + configCode)
  script.runInNewContext(sandbox)

  var oldConfig = sandbox.oldConfig
  if (oldConfig) {
    // Note: the systemjs loader need `CSS` upper case.
    oldConfig.buildCSS = options.buildCss || false
    oldConfig.separateCSS = options.separateCss || false
    debug('update jspm config file: %s', configPath)
    fs.writeFileSync(configPath, 'System.config(' + JSON.stringify(oldConfig, null, 2) + ');')
  }
}

function getDefaultBundleOptions(opts, env) {
  opts = opts || {}
  var _opts = {
    bundleDeps: false,
    outFile: 'bundle' + (opts.name ? '-' + opts.name : '') + '.js',
    sourceMaps: 'inline',
    sourceMapContents: true,
    lowResSourceMaps: true,
    inject: false,
    minify: false,
    mangle: false,
    buildCss: true,
    separateCss: false
  }

  env = env || process.env.NODE_ENV
  // Set default options for production.
  if ('production' === env) {
    _opts.sourceMaps = false
    _opts.sourceMapContents = false
    _opts.lowResSourceMaps = false
    _opts.inject = true
    _opts.minify = true
    _opts.mangle = true
    _opts.buildCss = true
    _opts.separateCss = true
  }

  _opts = assign(_opts, opts)
  // make sure we have the `.js` suffix for outFile
  var ext = path.extname(_opts.outFile)
  if (!ext) {
    _opts.outFile += '.js'
  } else if ('.js' !== ext) {
    // Replace it with the .js suffix. e.g. `.jsx`.
    _opts.outFile = _opts.outFile.slice(0, -ext.length) + '.js'
  }
  return _opts
}

function convertBundleOptionsToStr(options) {
  var str = ' ' + options.outFile

  if (!options.sourceMaps) {
    str += ' --skip-source-maps'
  }
  if (options.sourceMapContents) {
    str += ' --source-map-contents'
  }
  if (options.inject) {
    str += ' --inject'
  }
  if (options.minify) {
    str += ' --minify'
  }
  if (!options.mangle) {
    str += ' --no-mangle'
  }

  return str
}
<|endoftext|>


<commit_before>var dashboardView = Backbone.View.extend({
  el: '#content',
  updateInterval: 3000,
  clients: [],
  systems: [],
  convertedData: {},

  initialize: function () {
    //notes
    //this.collection.fetch();
    //this.options.description.fetch();
    var self = this;

    this.collection.fetch({
      success: function() {
        self.renderCharts();
        window.setInterval(function() {
          self.collection.fetch({success: function() {self.updateSystems()}});
        }, self.updateInterval);
      }
    });

  },

  template: new EJS({url: 'js/templates/dashboardView.ejs'}),

  render: function() {
    var self = this;
    $(this.el).html(this.template.text);

    $.each(this.options.description.models[0].attributes.groups, function(key, val) {
      $('#content').append(
        '
' +
        '
'+this.name+'
' +
        '
');
    });
    $.each(this.options.description.models[0].attributes.figures, function(key, val) {
      if (this.group === 'system') {
        self.renderSystem(this.identifier, this.name, this.description, this.type, this.units);
        self.systems.push(this.identifier);
      }
      else if (this.group === 'client') {
        self.renderClient(this.identifier, this.name, this.description, this.type, this.units);
        self.clients.push(this.identifier);
      }
    });

    return this;
  },
  renderCharts: function () {
    var self = this;
    this.generateClientData();

    $.each(self.clients, function(key, client) {
      var client = client;
    var test1 = self.sinAndCos();
    var test2 = self.generateD3Input(self.convertedData[client], client, "#ff7f0e");
 //console.log(test1);
 console.log(test2);
      nv.addGraph(function() {
        var chart = nv.models.lineChart();

        chart.xAxis
          .axisLabel('Time (ms)')
          .tickFormat(d3.format(',r'));
        chart.yAxis
          .axisLabel('Voltage (v)')
          .tickFormat(d3.format('.02f'));

        d3.select("#"+client+"Chart svg")
          .datum(self.generateD3Input(self.convertedData[client], client, "#ff7f0e"))
          //.datum(self.sinAndCos())
          .transition().duration(500)
          .call(chart)

        nv.utils.windowResize(function() { d3.select('#httpConnectionsChart svg').call(chart) });

        return chart;
      });

    });
  },

  generateClientData: function () {
    var self = this;
    var x = []; //Value
    var y = []; //Time
    var tempName;
    var tempArray = [];
    //FOR TESTING
    var i = 0;
    $.each(this.collection.models[0].attributes.client, function(key, val) {
      tempName = key;
      if (val.counts === undefined) {
        console.log("undefined");
      }
      else {
        tempArray = [];
        $.each(val.counts, function(k,v) {
          tempArray.push({x:i, y:v});
          self.convertedData[tempName] = tempArray;
          i++;
        });
      }
    });
  },

  generateD3Input: function (values, key, color) {
    if (values === undefined) {
      return;
    }
    return [
      {
        values: values,
        key: key,
        color: color
      }
    ]
  },

  sinAndCos: function () {
    var sin = [],
    cos = [];

    for (var i = 0; i < 50; i++) {
      sin.push({x: i, y: Math.sin(i/10)});
      cos.push({x: i, y: .5 * Math.cos(i/10)});
    }

    return [
      {
      values: sin,
      key: 'Sine Wave',
      color: '#ff7f0e'
    },
    {
      values: cos,
      key: 'Cosine Wave',
      color: '#2ca02c'
    }
    ];
  },

  renderClient: function (identifier, name, desc, type, units) {
    $('#client').append(
      '
' +
      '
' + name + '
' +
      '
<svg class="svgClass"/>
' +
      '
'
    );
  },
  renderSystem: function (identifier, name, desc, type, units) {
    $('#system').append(
      '
' +
      '

' +
      ''+ name +'	' +
      ''+ 'counting...' +'	' +
      '

' +
      '
'
    );
  },
  updateClient: function (identifier, count, counts) {

  },
  updateSystems: function () {
    $.each(this.collection.models[0].attributes.system, function(key, val) {
      $('#'+key+' .updateValue').html(val);
    });

  }

});
<commit_msg>updated dashboard<commit_after>var dashboardView = Backbone.View.extend({
  el: '#content',
  updateInterval: 6000,
  clients: [],
  systems: [],
  convertedData: {},
  oldSum: {},

  initialize: function () {
    var self = this;

    this.collection.fetch({
      success: function() {
        self.renderCharts();
        window.setInterval(function() {
          self.collection.fetch({
            success: function() {
              self.updateSystems();
              self.updateCharts();
            }
          });
        }, self.updateInterval);
      }
    });
  },

  template: new EJS({url: 'js/templates/dashboardView.ejs'}),

  render: function() {
    var self = this;
    $(this.el).html(this.template.text);

    $.each(this.options.description.models[0].attributes.groups, function(key, val) {
      $('#content').append(
        '
' +
        '
'+this.name+'
' +
        '
');
    });
    $.each(this.options.description.models[0].attributes.figures, function(key, val) {
      if (this.group === 'system') {
        self.renderSystem(this.identifier, this.name, this.description, this.type, this.units);
        self.systems.push(this.identifier);
      }
      else if (this.group === 'client') {
        self.renderClient(this.identifier, this.name, this.description, this.type, this.units);
        self.clients.push(this.identifier);
      }
    });
    if (this.collection.models[0] === undefined) {
      this.collection.fetch({
        success: function() {
          self.renderCharts();
        }
      });
    }
    else {
      self.renderCharts();
    }

    return this;
  },
  renderCharts: function () {
    var self = this;
    this.generateClientData();

    $.each(self.clients, function(key, client) {
      var client = client;
      nv.addGraph(function() {
        var chart = nv.models.lineChart();

        chart.xAxis
        .axisLabel('Time')

        chart.yAxis
        .axisLabel('Values')

        d3.select("#"+client+"Chart svg")
        .datum(self.generateD3Input(self.convertedData[client], client, "#8AA051"))
        .transition().duration(500)
        .call(chart)

        nv.utils.windowResize(function() {
          d3.select('#httpConnectionsChart svg').call(chart);
        });

        return chart;
      });

    });
  },

  updateCharts: function () {
    var self = this;
    this.generateClientData();

    $.each(self.clients, function(key, client) {
      var client = client;

      var chart = nv.models.lineChart();

      chart.xAxis
      .axisLabel("Time (ms)")

      chart.yAxis
      .axisLabel("Values");

      d3.select("#"+client+"Chart svg")
      .datum(self.generateD3Input(self.convertedData[client], client, "#8AA051"))
      .transition().duration(500)
      .call(chart)

      d3.selectAll('.nv-x text').text(function(d){
        if (isNaN(d)) {
          return d;
        }
        else {
          var date = new Date(d*1000);
          var hours = date.getHours();
          var minutes = date.getMinutes();
          var seconds = date.getSeconds();
          return hours+":"+minutes+":"+seconds;
        }
      });

    });

  },

  generateClientData: function () {
    var self = this;
    var x = []; //Value
    var y = []; //Time
    var tempName;
    var tempArray = [];
    $.each(this.collection.models[0].attributes.client, function(key, val) {
      tempName = key;
      if (val.sum === undefined) {
      }
      else {
        if (self.convertedData[tempName] === undefined) {
          self.convertedData[tempName] = [];
        }
        if (self.convertedData[tempName].length === 0) {
          self.oldSum[tempName] = val.sum;
          tempArray = self.convertedData[tempName];
          var timeStamp = Math.round(+new Date()/1000);
          tempArray.push({x:timeStamp, y:0});
        }
        if (self.convertedData[tempName].length !== 0)  {
          tempArray = self.convertedData[tempName];
          var calculatedSum = val.sum - self.oldSum[tempName];
          var timeStamp = Math.round(+new Date()/1000);

          self.oldSum[tempName] = val.sum;
          tempArray.push({x:timeStamp, y:calculatedSum});
        }

      }
    });
  },

  generateD3Input: function (values, key, color) {
    if (values === undefined) {
      return;
    }
    return [
      {
        values: values,
        key: key,
        color: color
      }
    ]
  },

  renderClient: function (identifier, name, desc, type, units) {
    $('#client').append(
      '
' +
      '
' + name + '
' +
      '
<svg class="svgClass"/>
' +
      '
'
    );
  },
  renderSystem: function (identifier, name, desc, type, units) {
    $('#system').append(
      '
' +
      '

' +
      ''+ name +'	' +
      ''+ 'counting...' +'	' +
      '

' +
      '
'
    );
  },
  updateClient: function (identifier, count, counts) {

  },
  updateSystems: function () {
    $.each(this.collection.models[0].attributes.system, function(key, val) {
      $('#'+key+' .updateValue').html(val);
    });

  }

});
<|endoftext|>


<commit_before>"use strict";

(function () {
    var video             = document.getElementById('video-player'),
        videoControls     = document.getElementById('controls'),
        play              = document.getElementById('action'),
        playTime          = document.getElementById('play-time'),
        totalTime         = document.getElementById('total-time'),
        progressContainer = document.getElementById('progress'),
        playProgressBar   = document.getElementById('bar'),
        volume            = document.getElementById('volume'),
        volumeTick        = document.querySelectorAll('.volume-mark'),
        VideoPlayer;

    VideoPlayer = {
        init: function () {
            video.removeAttribute('controls');

            if (video.readyState > 0) {
                // metadata is loaded already, fire event handler manually
                VideoPlayer.initializeControls();
            } else {
                video.addEventListener("loadedmetadata", VideoPlayer.initializeControls, false);
            }

            // handle button presses/click events
            VideoPlayer.handleButtonPresses();
        },

        initializeControls: function () {
            // setup customized controls
            totalTime.innerHTML = VideoPlayer.convertTime(video.duration);

            VideoPlayer.showHideControls();
        },

        showHideControls: function () {
            // attach hover events, try to account for IE
            if (video.addEventListener && videoControls.addEventListener) {
                video.addEventListener('mouseover', VideoPlayer.showControls, false);
                video.addEventListener('mouseout', VideoPlayer.hideControls, false);
                videoControls.addEventListener('mouseover', VideoPlayer.showControls, false);
                videoControls.addEventListener('mouseout', VideoPlayer.hideControls, false);
            } else {
                video.attachEvent('onmouseover', VideoPlayer.showControls, false);
                video.attachEvent('onmouseout', VideoPlayer.hideControls, false);
                videoControls.attachEvent('onmouseover', VideoPlayer.showControls, false);
                videoControls.attachEvent('onmouseout', VideoPlayer.hideControls, false);
            }
        },

        showControls: function () {
            videoControls.style.opacity = 1;
            videoControls.style.zIndex = 2;
        },

        hideControls: function () {
            videoControls.style.opacity = 0;
            videoControls.style.zIndex = -1;
        },

        handleButtonPresses: function () {
            video.addEventListener('click', VideoPlayer.playORpause, false);
            play.addEventListener('click', VideoPlayer.playORpause, false);
            volumeTick.addEventListener('click', VideoPlayer.adjustVolume, false);

            video.addEventListener('play', function() {
                play.title = 'Pause';
                play.childNodes[0].classList.remove("icon-play");
                play.childNodes[0].className += " icon-pause";
                VideoPlayer.trackPlayProgress();
            }, false);

            video.addEventListener('pause', function() {
                play.title = 'Play';
                play.childNodes[0].classList.remove("icon-pause");
                play.childNodes[0].className += " icon-play";
                VideoPlayer.stopTrackingPlayProgress();
            }, false);

            video.addEventListener('ended', function() {
                this.currentTime = 0;
                this.pause();
            }, false);
        },

        playORpause: function () {
            if (video.paused || video.ended) {
                if (video.ended) {
                    video.currentTime = 0;
                }
                video.play();
            } else {
                video.pause();
            }
        },

        adjustVolume: function (event) {
            var level = event.target.getAttribute('id');

            if (level === "master-volume") {
                if (video.volume > 0) {
                    video.volume = 0;
                } else {
                    video.volume = 1;
                }
            } else {
                level = parseInt(level);
                if (isNaN(level)) {
                    console.log("NaN, not changing level");
                } else {
                    if (level === 1) {
                        video.volume = 1;
                    } else {
                        video.volume = level * 0.1;
                    }
                }
            }
        },

        trackPlayProgress: function () {},

        updatePlayProgress: function () {},

        stopTrackingPlayProgress: function () {},

        convertTime: function (time) {
            var minutes = parseInt(time / 60, 10),
                seconds = parseInt(time % 60, 10),
                str;

            if (seconds < 10) {}
                seconds = '0' + seconds;
            }

            str = minutes + ':' + seconds;
            return str;
        }
    };

    VideoPlayer.init()
}());
<commit_msg>Various methods to track play progress<commit_after>"use strict";

(function () {
    var video             = document.getElementById('video-player'),
        videoControls     = document.getElementById('controls'),
        play              = document.getElementById('action'),
        playTime          = document.getElementById('play-time'),
        totalTime         = document.getElementById('total-time'),
        progressContainer = document.getElementById('progress'),
        playProgressBar   = document.getElementById('bar'),
        volume            = document.getElementById('volume'),
        volumeTick        = document.querySelectorAll('.volume-mark'),
        VideoPlayer;

    VideoPlayer = {
        init: function () {
            video.removeAttribute('controls');

            if (video.readyState > 0) {
                // metadata is loaded already, fire event handler manually
                VideoPlayer.initializeControls();
            } else {
                video.addEventListener("loadedmetadata", VideoPlayer.initializeControls, false);
            }

            // handle button presses/click events
            VideoPlayer.handleButtonPresses();
        },

        initializeControls: function () {
            // setup customized controls
            totalTime.innerHTML = VideoPlayer.convertTime(video.duration);

            VideoPlayer.showHideControls();
        },

        showHideControls: function () {
            // attach hover events, try to account for IE
            if (video.addEventListener && videoControls.addEventListener) {
                video.addEventListener('mouseover', VideoPlayer.showControls, false);
                video.addEventListener('mouseout', VideoPlayer.hideControls, false);
                videoControls.addEventListener('mouseover', VideoPlayer.showControls, false);
                videoControls.addEventListener('mouseout', VideoPlayer.hideControls, false);
            } else {
                video.attachEvent('onmouseover', VideoPlayer.showControls, false);
                video.attachEvent('onmouseout', VideoPlayer.hideControls, false);
                videoControls.attachEvent('onmouseover', VideoPlayer.showControls, false);
                videoControls.attachEvent('onmouseout', VideoPlayer.hideControls, false);
            }
        },

        showControls: function () {
            videoControls.style.opacity = 1;
            videoControls.style.zIndex = 2;
        },

        hideControls: function () {
            videoControls.style.opacity = 0;
            videoControls.style.zIndex = -1;
        },

        handleButtonPresses: function () {
            video.addEventListener('click', VideoPlayer.playORpause, false);
            play.addEventListener('click', VideoPlayer.playORpause, false);
            volumeTick.addEventListener('click', VideoPlayer.adjustVolume, false);

            video.addEventListener('play', function() {
                play.title = 'Pause';
                play.childNodes[0].classList.remove("icon-play");
                play.childNodes[0].className += " icon-pause";
                VideoPlayer.trackPlayProgress();
            }, false);

            video.addEventListener('pause', function() {
                play.title = 'Play';
                play.childNodes[0].classList.remove("icon-pause");
                play.childNodes[0].className += " icon-play";
                VideoPlayer.stopTrackingPlayProgress();
            }, false);

            video.addEventListener('ended', function() {
                this.currentTime = 0;
                this.pause();
            }, false);
        },

        playORpause: function () {
            if (video.paused || video.ended) {
                if (video.ended) {
                    video.currentTime = 0;
                }
                video.play();
            } else {
                video.pause();
            }
        },

        adjustVolume: function (event) {
            var level = event.target.getAttribute('id');

            if (level === "master-volume") {
                if (video.volume > 0) {
                    video.volume = 0;
                } else {
                    video.volume = 1;
                }
            } else {
                level = parseInt(level);
                if (isNaN(level)) {
                    console.log("NaN, not changing level");
                } else {
                    if (level === 1) {
                        video.volume = 1;
                    } else {
                        video.volume = level * 0.1;
                    }
                }
            }
        },

        trackPlayProgress: function () {
            (progressTrack = function () {
                VideoPlayer.updatePlayProgress();
                VideoPlayer.playProgressInterval = setTimeout(progressTrack, 50);
            })();
        },

        updatePlayProgress: function () {
            playTime.innerHTML = VideoPlayer.convertTime(video.currentTime);
            playProgressBar.style.width = parseInt(((video.currentTime / video.duration) * 100), 10) + "%";
        },

        stopTrackingPlayProgress: function () {
            clearTimeout(VideoPlayer.playProgressInterval);
        },

        convertTime: function (time) {
            var minutes = parseInt(time / 60, 10),
                seconds = parseInt(time % 60, 10),
                str;

            if (seconds < 10) {}
                seconds = '0' + seconds;
            }

            str = minutes + ':' + seconds;
            return str;
        }
    };

    VideoPlayer.init()
}());
<|endoftext|>


<commit_before>var Whitelist = {
  middleware: function middleware(keys) {
    return function(req, res, next) {
      var json = res.json;
      res.json = function(object, status) {
        res.json = json;

        object = Whitelist.only(object, keys);
        res.json(status, object);
      };
      next();
    };
  },

  only: function only(object, keys) {
    object = object || {};
    if('string' === typeof keys) {
      keys = keys.split(/ +/);
    }

    var filter = function filter(obj, keys) {
      return keys.reduce(function(ret, key){
        ret[key] = obj[key];
        return ret;
      }, {});
    };

    if(Array.isArray(object)) {
      object = object.map(function(obj) { return filter(obj, keys); });
    } else {
      object = filter(object, keys);
    }

    return object;
  }
};

module.exports = Whitelist;
<commit_msg>Fixes for express 3.x<commit_after>var Whitelist = {
  middleware: function middleware(keys) {
    return function(req, res, next) {
      var json = res.json;
      res.json = function(statusCode, object) {
        if('object' === typeof statusCode) {
          object = statusCode;
          statusCode = object;
        }

        res.json = json;

        object = Whitelist.only(object, keys);

        res.status(statusCode).json(object);
      };
      next();
    };
  },

  only: function only(object, keys) {
    object = object || {};
    if('string' === typeof keys) {
      keys = keys.split(/ +/);
    }

    var filter = function filter(obj, keys) {
      return keys.reduce(function(ret, key){
        ret[key] = obj[key];
        return ret;
      }, {});
    };

    if(Array.isArray(object)) {
      object = object.map(function(obj) { return filter(obj, keys); });
    } else {
      object = filter(object, keys);
    }

    return object;
  }
};

module.exports = Whitelist;
<|endoftext|>