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

var tokenize = function(/*String*/ str, /*RegExp*/ re, /*Function?*/ parseDelim, /*Object?*/ instance){
  // summary:
  //    Split a string by a regular expression with the ability to capture the delimeters
  // parseDelim:
  //    Each group (excluding the 0 group) is passed as a parameter. If the function returns
  //    a value, it's added to the list of tokens.
  // instance:
  //    Used as the "this' instance when calling parseDelim
  var tokens = [];
  var match, content, lastIndex = 0;
  while((match = re.exec(str))){
    content = str.slice(lastIndex, re.lastIndex - match[0].length);
    if(content.length){
      tokens.push(content);
    }
    if(parseDelim){
      var parsed = parseDelim.apply(instance, match.slice(1).concat(tokens.length));
      if(typeof parsed != 'undefined'){
        if(parsed.specifier === '%'){
          tokens.push('%');
        }else{
          tokens.push(parsed);
        }
      }
    }
    lastIndex = re.lastIndex;
  }
  content = str.slice(lastIndex);
  if(content.length){
    tokens.push(content);
  }
  return tokens;
};

var Formatter = function(/*String*/ format){
  this._mapped = false;
  this._format = format;
  this._tokens = tokenize(format, this._re, this._parseDelim, this);
};

Formatter.prototype._re = /\%(?:\(([\w_.]+)\)|([1-9]\d*)\$)?([0 +\-\#]*)(\*|\d+)?(\.)?(\*|\d+)?[hlL]?([\%bscdeEfFgGioOuxX])/g;
Formatter.prototype._parseDelim = function(mapping, intmapping, flags, minWidth, period, precision, specifier){
  if(mapping){
    this._mapped = true;
  }
  return {
    mapping: mapping,
    intmapping: intmapping,
    flags: flags,
    _minWidth: minWidth, // May be dependent on parameters
    period: period,
    _precision: precision, // May be dependent on parameters
    specifier: specifier
  };
};
Formatter.prototype._specifiers = {
  b: {
    base: 2,
    isInt: true
  },
  o: {
    base: 8,
    isInt: true
  },
  x: {
    base: 16,
    isInt: true
  },
  X: {
    extend: ['x'],
    toUpper: true
  },
  d: {
    base: 10,
    isInt: true
  },
  i: {
    extend: ['d']
  },
  u: {
    extend: ['d'],
    isUnsigned: true
  },
  c: {
    setArg: function(token){
      if(!isNaN(token.arg)){
        var num = parseInt(token.arg);
        if(num < 0 || num > 127){
          throw new Error('invalid character code passed to %c in printf');
        }
        token.arg = isNaN(num) ? '' + num : String.fromCharCode(num);
      }
    }
  },
  s: {
    setMaxWidth: function(token){
      token.maxWidth = (token.period == '.') ? token.precision : -1;
    }
  },
  e: {
    isDouble: true,
    doubleNotation: 'e'
  },
  E: {
    extend: ['e'],
    toUpper: true
  },
  f: {
    isDouble: true,
    doubleNotation: 'f'
  },
  F: {
    extend: ['f']
  },
  g: {
    isDouble: true,
    doubleNotation: 'g'
  },
  G: {
    extend: ['g'],
    toUpper: true
  },
  O: {
    isObject: true
  }
};
Formatter.prototype.format = function(/*mixed...*/ filler){
  if(this._mapped && typeof filler != 'object'){
    throw new Error('format requires a mapping');
  }

  var str = '';
  var position = 0;
  for(var i = 0, token; i < this._tokens.length; i++){
    token = this._tokens[i];

    if(typeof token == 'string'){
      str += token;
    }else{
      if(this._mapped){
        // Identify value of property defined in `token.mapping`
        var tokens = token.mapping.split('.');
        var value = filler;
        for (var j = 0, c = tokens.length; j < c; j++) {
          value = value[tokens[j]];
          if (typeof value === 'undefined') {
            break
          }
        }
        if(typeof value == 'undefined'){
          throw new Error('missing key ' + token.mapping);
        }
        token.arg = value;
      }else{
        if(token.intmapping){
          position = parseInt(token.intmapping) - 1;
        }
        if(position >= arguments.length){
          throw new Error('got ' + arguments.length + ' printf arguments, insufficient for \'' + this._format + '\'');
        }
        token.arg = arguments[position++];
      }

      if(!token.compiled){
        token.compiled = true;
        token.sign = '';
        token.zeroPad = false;
        token.rightJustify = false;
        token.alternative = false;

        var flags = {};
        for(var fi = token.flags.length; fi--;){
          var flag = token.flags.charAt(fi);
          flags[flag] = true;
          switch(flag){
            case ' ':
              token.sign = ' ';
              break;
            case '+':
              token.sign = '+';
              break;
            case '0':
              token.zeroPad = (flags['-']) ? false : true;
              break;
            case '-':
              token.rightJustify = true;
              token.zeroPad = false;
              break;
            case '#':
              token.alternative = true;
              break;
            default:
              throw Error('bad formatting flag \'' + token.flags.charAt(fi) + '\'');
          }
        }

        token.minWidth = (token._minWidth) ? parseInt(token._minWidth) : 0;
        token.maxWidth = -1;
        token.toUpper = false;
        token.isUnsigned = false;
        token.isInt = false;
        token.isDouble = false;
        token.isObject = false;
        token.precision = 1;
        if(token.period == '.'){
          if(token._precision){
            token.precision = parseInt(token._precision);
          }else{
            token.precision = 0;
          }
        }

        var mixins = this._specifiers[token.specifier];
        if(typeof mixins == 'undefined'){
          throw new Error('unexpected specifier \'' + token.specifier + '\'');
        }
        if(mixins.extend){
          var s = this._specifiers[mixins.extend];
          for(var k in s){
            mixins[k] = s[k];
          }
          delete mixins.extend;
        }
        for(var l in mixins){
          token[l] = mixins[l];
        }
      }

      if(typeof token.setArg == 'function'){
        token.setArg(token);
      }

      if(typeof token.setMaxWidth == 'function'){
        token.setMaxWidth(token);
      }

      if(token._minWidth == '*'){
        if(this._mapped){
          throw new Error('* width not supported in mapped formats');
        }
        token.minWidth = parseInt(arguments[position++]);
        if(isNaN(token.minWidth)){
          throw new Error('the argument for * width at position ' + position + ' is not a number in ' + this._format);
        }
        // negative width means rightJustify
        if (token.minWidth < 0) {
          token.rightJustify = true;
          token.minWidth = -token.minWidth;
        }
      }

      if(token._precision == '*' && token.period == '.'){
        if(this._mapped){
          throw new Error('* precision not supported in mapped formats');
        }
        token.precision = parseInt(arguments[position++]);
        if(isNaN(token.precision)){
          throw Error('the argument for * precision at position ' + position + ' is not a number in ' + this._format);
        }
        // negative precision means unspecified
        if (token.precision < 0) {
          token.precision = 1;
          token.period = '';
        }
      }
      if(token.isInt){
        // a specified precision means no zero padding
        if(token.period == '.'){
          token.zeroPad = false;
        }
        this.formatInt(token);
      }else if(token.isDouble){
        if(token.period != '.'){
          token.precision = 6;
        }
        this.formatDouble(token);
      }else if(token.isObject){
        this.formatObject(token);
      }
      this.fitField(token);
      str += '' + token.arg;
    }
  }

  return str;
};
Formatter.prototype._zeros10 = '0000000000';
Formatter.prototype._spaces10 = '          ';
Formatter.prototype.formatInt = function(token) {
  var i = parseInt(token.arg);
  if(!isFinite(i)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
    // allow this only if arg is number
    if(typeof token.arg != 'number'){
      throw new Error('format argument \'' + token.arg + '\' not an integer; parseInt returned ' + i);
    }
    //return '' + i;
    i = 0;
  }

  // if not base 10, make negatives be positive
  // otherwise, (-10).toString(16) is '-a' instead of 'fffffff6'
  if(i < 0 && (token.isUnsigned || token.base != 10)){
    i = 0xffffffff + i + 1;
  }

  if(i < 0){
    token.arg = (- i).toString(token.base);
    this.zeroPad(token);
    token.arg = '-' + token.arg;
  }else{
    token.arg = i.toString(token.base);
    // need to make sure that argument 0 with precision==0 is formatted as ''
    if(!i && !token.precision){
      token.arg = '';
    }else{
      this.zeroPad(token);
    }
    if(token.sign){
      token.arg = token.sign + token.arg;
    }
  }
  if(token.base == 16){
    if(token.alternative){
      token.arg = '0x' + token.arg;
    }
    token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
  }
  if(token.base == 8){
    if(token.alternative && token.arg.charAt(0) != '0'){
      token.arg = '0' + token.arg;
    }
  }
};
Formatter.prototype.formatDouble = function(token) {
  var f = parseFloat(token.arg);
  if(!isFinite(f)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
    // allow this only if arg is number
    if(typeof token.arg != 'number'){
      throw new Error('format argument \'' + token.arg + '\' not a float; parseFloat returned ' + f);
    }
    // C99 says that for 'f':
    //   infinity -> '[-]inf' or '[-]infinity' ('[-]INF' or '[-]INFINITY' for 'F')
    //   NaN -> a string  starting with 'nan' ('NAN' for 'F')
    // this is not commonly implemented though.
    //return '' + f;
    f = 0;
  }

  switch(token.doubleNotation) {
    case 'e': {
      token.arg = f.toExponential(token.precision);
      break;
    }
    case 'f': {
      token.arg = f.toFixed(token.precision);
      break;
    }
    case 'g': {
      // C says use 'e' notation if exponent is < -4 or is >= prec
      // ECMAScript for toPrecision says use exponential notation if exponent is >= prec,
      // though step 17 of toPrecision indicates a test for < -6 to force exponential.
      if(Math.abs(f) < 0.0001){
        //print('forcing exponential notation for f=' + f);
        token.arg = f.toExponential(token.precision > 0 ? token.precision - 1 : token.precision);
      }else{
        token.arg = f.toPrecision(token.precision);
      }

      // In C, unlike 'f', 'gG' removes trailing 0s from fractional part, unless alternative format flag ('#').
      // But ECMAScript formats toPrecision as 0.00100000. So remove trailing 0s.
      if(!token.alternative){
        //print('replacing trailing 0 in \'' + s + '\'');
        token.arg = token.arg.replace(/(\..*[^0])0*e/, '$1e');
        // if fractional part is entirely 0, remove it and decimal point
        token.arg = token.arg.replace(/\.0*e/, 'e').replace(/\.0$/,'');
      }
      break;
    }
    default: throw new Error('unexpected double notation \'' + token.doubleNotation + '\'');
  }

  // C says that exponent must have at least two digits.
  // But ECMAScript does not; toExponential results in things like '1.000000e-8' and '1.000000e+8'.
  // Note that s.replace(/e([\+\-])(\d)/, 'e$10$2') won't work because of the '$10' instead of '$1'.
  // And replace(re, func) isn't supported on IE50 or Safari1.
  token.arg = token.arg.replace(/e\+(\d)$/, 'e+0$1').replace(/e\-(\d)$/, 'e-0$1');

  // if alt, ensure a decimal point
  if(token.alternative){
    token.arg = token.arg.replace(/^(\d+)$/,'$1.');
    token.arg = token.arg.replace(/^(\d+)e/,'$1.e');
  }

  if(f >= 0 && token.sign){
    token.arg = token.sign + token.arg;
  }

  token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
};
Formatter.prototype.formatObject = function(token) {
  // If no precision is specified, then reset it to null (infinite depth).
  var precision = (token.period === '.') ? token.precision : null;
  // Historically, inspect was called with 3 options
  // token.arg = util.inspect(token.arg, !token.alternative, precision, token.sign);
  // Now using an object but not sure colors make any sense here
  token.arg = util.inspect(token.arg, {
    showHidden: !token.alternative,
    depth: precision,
    colors: token.sign,
    compact: true
  });
};
Formatter.prototype.zeroPad = function(token, /*Int*/ length) {
  length = (arguments.length == 2) ? length : token.precision;
  var negative = false;
  if(typeof token.arg != "string"){
    token.arg = "" + token.arg;
  }
  if (token.arg.substr(0,1) === '-') {
    negative = true;
    token.arg = token.arg.substr(1);
  }

  var tenless = length - 10;
  while(token.arg.length < tenless){
    token.arg = (token.rightJustify) ? token.arg + this._zeros10 : this._zeros10 + token.arg;
  }
  var pad = length - token.arg.length;
  token.arg = (token.rightJustify) ? token.arg + this._zeros10.substring(0, pad) : this._zeros10.substring(0, pad) + token.arg;
  if (negative) token.arg = '-' + token.arg;
};
Formatter.prototype.fitField = function(token) {
  if(token.maxWidth >= 0 && token.arg.length > token.maxWidth){
    token.arg = token.arg.substring(0, token.maxWidth);
  }
  if(token.zeroPad){
    this.zeroPad(token, token.minWidth);
    return;
  }
  this.spacePad(token);
};
Formatter.prototype.spacePad = function(token, /*Int*/ length) {
  length = (arguments.length == 2) ? length : token.minWidth;
  if(typeof token.arg != 'string'){
    token.arg = '' + token.arg;
  }
  var tenless = length - 10;
  while(token.arg.length < tenless){
    token.arg = (token.rightJustify) ? token.arg + this._spaces10 : this._spaces10 + token.arg;
  }
  var pad = length - token.arg.length;
  token.arg = (token.rightJustify) ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg;
};

module.exports = function(){
  var args = Array.prototype.slice.call(arguments),
    stream, format;
  if(args[0] instanceof require('stream').Stream){
    stream = args.shift();
  }
  format = args.shift();
  var formatter = new Formatter(format);
  var string = formatter.format.apply(formatter, args);
  if(stream){
    stream.write(string);
  }else{
    return string;
  }
};

module.exports.Formatter = Formatter;
<commit_msg>Better error message when a key is missing<commit_after>
var util = require('util');

var tokenize = function(/*String*/ str, /*RegExp*/ re, /*Function?*/ parseDelim, /*Object?*/ instance){
  // summary:
  //    Split a string by a regular expression with the ability to capture the delimeters
  // parseDelim:
  //    Each group (excluding the 0 group) is passed as a parameter. If the function returns
  //    a value, it's added to the list of tokens.
  // instance:
  //    Used as the "this' instance when calling parseDelim
  var tokens = [];
  var match, content, lastIndex = 0;
  while((match = re.exec(str))){
    content = str.slice(lastIndex, re.lastIndex - match[0].length);
    if(content.length){
      tokens.push(content);
    }
    if(parseDelim){
      var parsed = parseDelim.apply(instance, match.slice(1).concat(tokens.length));
      if(typeof parsed != 'undefined'){
        if(parsed.specifier === '%'){
          tokens.push('%');
        }else{
          tokens.push(parsed);
        }
      }
    }
    lastIndex = re.lastIndex;
  }
  content = str.slice(lastIndex);
  if(content.length){
    tokens.push(content);
  }
  return tokens;
};

var Formatter = function(/*String*/ format){
  this._mapped = false;
  this._format = format;
  this._tokens = tokenize(format, this._re, this._parseDelim, this);
};

Formatter.prototype._re = /\%(?:\(([\w_.]+)\)|([1-9]\d*)\$)?([0 +\-\#]*)(\*|\d+)?(\.)?(\*|\d+)?[hlL]?([\%bscdeEfFgGioOuxX])/g;
Formatter.prototype._parseDelim = function(mapping, intmapping, flags, minWidth, period, precision, specifier){
  if(mapping){
    this._mapped = true;
  }
  return {
    mapping: mapping,
    intmapping: intmapping,
    flags: flags,
    _minWidth: minWidth, // May be dependent on parameters
    period: period,
    _precision: precision, // May be dependent on parameters
    specifier: specifier
  };
};
Formatter.prototype._specifiers = {
  b: {
    base: 2,
    isInt: true
  },
  o: {
    base: 8,
    isInt: true
  },
  x: {
    base: 16,
    isInt: true
  },
  X: {
    extend: ['x'],
    toUpper: true
  },
  d: {
    base: 10,
    isInt: true
  },
  i: {
    extend: ['d']
  },
  u: {
    extend: ['d'],
    isUnsigned: true
  },
  c: {
    setArg: function(token){
      if(!isNaN(token.arg)){
        var num = parseInt(token.arg);
        if(num < 0 || num > 127){
          throw new Error('invalid character code passed to %c in printf');
        }
        token.arg = isNaN(num) ? '' + num : String.fromCharCode(num);
      }
    }
  },
  s: {
    setMaxWidth: function(token){
      token.maxWidth = (token.period == '.') ? token.precision : -1;
    }
  },
  e: {
    isDouble: true,
    doubleNotation: 'e'
  },
  E: {
    extend: ['e'],
    toUpper: true
  },
  f: {
    isDouble: true,
    doubleNotation: 'f'
  },
  F: {
    extend: ['f']
  },
  g: {
    isDouble: true,
    doubleNotation: 'g'
  },
  G: {
    extend: ['g'],
    toUpper: true
  },
  O: {
    isObject: true
  }
};
Formatter.prototype.format = function(/*mixed...*/ filler){
  if(this._mapped && typeof filler != 'object'){
    throw new Error('format requires a mapping');
  }

  var str = '';
  var position = 0;
  for(var i = 0, token; i < this._tokens.length; i++){
    token = this._tokens[i];

    if(typeof token == 'string'){
      str += token;
    }else{
      if(this._mapped){
        // Identify value of property defined in `token.mapping`
        var tokens = token.mapping.split('.');
        var value = filler;
        for (var j = 0, c = tokens.length; j < c; j++) {
          value = value[tokens[j]];
          if (typeof value === 'undefined') {
            break
          }
        }
        if(typeof value == 'undefined'){
          throw new Error('missing key \'' + token.mapping + '\'');
        }
        token.arg = value;
      }else{
        if(token.intmapping){
          position = parseInt(token.intmapping) - 1;
        }
        if(position >= arguments.length){
          throw new Error('got ' + arguments.length + ' printf arguments, insufficient for \'' + this._format + '\'');
        }
        token.arg = arguments[position++];
      }

      if(!token.compiled){
        token.compiled = true;
        token.sign = '';
        token.zeroPad = false;
        token.rightJustify = false;
        token.alternative = false;

        var flags = {};
        for(var fi = token.flags.length; fi--;){
          var flag = token.flags.charAt(fi);
          flags[flag] = true;
          switch(flag){
            case ' ':
              token.sign = ' ';
              break;
            case '+':
              token.sign = '+';
              break;
            case '0':
              token.zeroPad = (flags['-']) ? false : true;
              break;
            case '-':
              token.rightJustify = true;
              token.zeroPad = false;
              break;
            case '#':
              token.alternative = true;
              break;
            default:
              throw Error('bad formatting flag \'' + token.flags.charAt(fi) + '\'');
          }
        }

        token.minWidth = (token._minWidth) ? parseInt(token._minWidth) : 0;
        token.maxWidth = -1;
        token.toUpper = false;
        token.isUnsigned = false;
        token.isInt = false;
        token.isDouble = false;
        token.isObject = false;
        token.precision = 1;
        if(token.period == '.'){
          if(token._precision){
            token.precision = parseInt(token._precision);
          }else{
            token.precision = 0;
          }
        }

        var mixins = this._specifiers[token.specifier];
        if(typeof mixins == 'undefined'){
          throw new Error('unexpected specifier \'' + token.specifier + '\'');
        }
        if(mixins.extend){
          var s = this._specifiers[mixins.extend];
          for(var k in s){
            mixins[k] = s[k];
          }
          delete mixins.extend;
        }
        for(var l in mixins){
          token[l] = mixins[l];
        }
      }

      if(typeof token.setArg == 'function'){
        token.setArg(token);
      }

      if(typeof token.setMaxWidth == 'function'){
        token.setMaxWidth(token);
      }

      if(token._minWidth == '*'){
        if(this._mapped){
          throw new Error('* width not supported in mapped formats');
        }
        token.minWidth = parseInt(arguments[position++]);
        if(isNaN(token.minWidth)){
          throw new Error('the argument for * width at position ' + position + ' is not a number in ' + this._format);
        }
        // negative width means rightJustify
        if (token.minWidth < 0) {
          token.rightJustify = true;
          token.minWidth = -token.minWidth;
        }
      }

      if(token._precision == '*' && token.period == '.'){
        if(this._mapped){
          throw new Error('* precision not supported in mapped formats');
        }
        token.precision = parseInt(arguments[position++]);
        if(isNaN(token.precision)){
          throw Error('the argument for * precision at position ' + position + ' is not a number in ' + this._format);
        }
        // negative precision means unspecified
        if (token.precision < 0) {
          token.precision = 1;
          token.period = '';
        }
      }
      if(token.isInt){
        // a specified precision means no zero padding
        if(token.period == '.'){
          token.zeroPad = false;
        }
        this.formatInt(token);
      }else if(token.isDouble){
        if(token.period != '.'){
          token.precision = 6;
        }
        this.formatDouble(token);
      }else if(token.isObject){
        this.formatObject(token);
      }
      this.fitField(token);
      str += '' + token.arg;
    }
  }

  return str;
};
Formatter.prototype._zeros10 = '0000000000';
Formatter.prototype._spaces10 = '          ';
Formatter.prototype.formatInt = function(token) {
  var i = parseInt(token.arg);
  if(!isFinite(i)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
    // allow this only if arg is number
    if(typeof token.arg != 'number'){
      throw new Error('format argument \'' + token.arg + '\' not an integer; parseInt returned ' + i);
    }
    //return '' + i;
    i = 0;
  }

  // if not base 10, make negatives be positive
  // otherwise, (-10).toString(16) is '-a' instead of 'fffffff6'
  if(i < 0 && (token.isUnsigned || token.base != 10)){
    i = 0xffffffff + i + 1;
  }

  if(i < 0){
    token.arg = (- i).toString(token.base);
    this.zeroPad(token);
    token.arg = '-' + token.arg;
  }else{
    token.arg = i.toString(token.base);
    // need to make sure that argument 0 with precision==0 is formatted as ''
    if(!i && !token.precision){
      token.arg = '';
    }else{
      this.zeroPad(token);
    }
    if(token.sign){
      token.arg = token.sign + token.arg;
    }
  }
  if(token.base == 16){
    if(token.alternative){
      token.arg = '0x' + token.arg;
    }
    token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
  }
  if(token.base == 8){
    if(token.alternative && token.arg.charAt(0) != '0'){
      token.arg = '0' + token.arg;
    }
  }
};
Formatter.prototype.formatDouble = function(token) {
  var f = parseFloat(token.arg);
  if(!isFinite(f)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
    // allow this only if arg is number
    if(typeof token.arg != 'number'){
      throw new Error('format argument \'' + token.arg + '\' not a float; parseFloat returned ' + f);
    }
    // C99 says that for 'f':
    //   infinity -> '[-]inf' or '[-]infinity' ('[-]INF' or '[-]INFINITY' for 'F')
    //   NaN -> a string  starting with 'nan' ('NAN' for 'F')
    // this is not commonly implemented though.
    //return '' + f;
    f = 0;
  }

  switch(token.doubleNotation) {
    case 'e': {
      token.arg = f.toExponential(token.precision);
      break;
    }
    case 'f': {
      token.arg = f.toFixed(token.precision);
      break;
    }
    case 'g': {
      // C says use 'e' notation if exponent is < -4 or is >= prec
      // ECMAScript for toPrecision says use exponential notation if exponent is >= prec,
      // though step 17 of toPrecision indicates a test for < -6 to force exponential.
      if(Math.abs(f) < 0.0001){
        //print('forcing exponential notation for f=' + f);
        token.arg = f.toExponential(token.precision > 0 ? token.precision - 1 : token.precision);
      }else{
        token.arg = f.toPrecision(token.precision);
      }

      // In C, unlike 'f', 'gG' removes trailing 0s from fractional part, unless alternative format flag ('#').
      // But ECMAScript formats toPrecision as 0.00100000. So remove trailing 0s.
      if(!token.alternative){
        //print('replacing trailing 0 in \'' + s + '\'');
        token.arg = token.arg.replace(/(\..*[^0])0*e/, '$1e');
        // if fractional part is entirely 0, remove it and decimal point
        token.arg = token.arg.replace(/\.0*e/, 'e').replace(/\.0$/,'');
      }
      break;
    }
    default: throw new Error('unexpected double notation \'' + token.doubleNotation + '\'');
  }

  // C says that exponent must have at least two digits.
  // But ECMAScript does not; toExponential results in things like '1.000000e-8' and '1.000000e+8'.
  // Note that s.replace(/e([\+\-])(\d)/, 'e$10$2') won't work because of the '$10' instead of '$1'.
  // And replace(re, func) isn't supported on IE50 or Safari1.
  token.arg = token.arg.replace(/e\+(\d)$/, 'e+0$1').replace(/e\-(\d)$/, 'e-0$1');

  // if alt, ensure a decimal point
  if(token.alternative){
    token.arg = token.arg.replace(/^(\d+)$/,'$1.');
    token.arg = token.arg.replace(/^(\d+)e/,'$1.e');
  }

  if(f >= 0 && token.sign){
    token.arg = token.sign + token.arg;
  }

  token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
};
Formatter.prototype.formatObject = function(token) {
  // If no precision is specified, then reset it to null (infinite depth).
  var precision = (token.period === '.') ? token.precision : null;
  // Historically, inspect was called with 3 options
  // token.arg = util.inspect(token.arg, !token.alternative, precision, token.sign);
  // Now using an object but not sure colors make any sense here
  token.arg = util.inspect(token.arg, {
    showHidden: !token.alternative,
    depth: precision,
    colors: token.sign,
    compact: true
  });
};
Formatter.prototype.zeroPad = function(token, /*Int*/ length) {
  length = (arguments.length == 2) ? length : token.precision;
  var negative = false;
  if(typeof token.arg != "string"){
    token.arg = "" + token.arg;
  }
  if (token.arg.substr(0,1) === '-') {
    negative = true;
    token.arg = token.arg.substr(1);
  }

  var tenless = length - 10;
  while(token.arg.length < tenless){
    token.arg = (token.rightJustify) ? token.arg + this._zeros10 : this._zeros10 + token.arg;
  }
  var pad = length - token.arg.length;
  token.arg = (token.rightJustify) ? token.arg + this._zeros10.substring(0, pad) : this._zeros10.substring(0, pad) + token.arg;
  if (negative) token.arg = '-' + token.arg;
};
Formatter.prototype.fitField = function(token) {
  if(token.maxWidth >= 0 && token.arg.length > token.maxWidth){
    token.arg = token.arg.substring(0, token.maxWidth);
  }
  if(token.zeroPad){
    this.zeroPad(token, token.minWidth);
    return;
  }
  this.spacePad(token);
};
Formatter.prototype.spacePad = function(token, /*Int*/ length) {
  length = (arguments.length == 2) ? length : token.minWidth;
  if(typeof token.arg != 'string'){
    token.arg = '' + token.arg;
  }
  var tenless = length - 10;
  while(token.arg.length < tenless){
    token.arg = (token.rightJustify) ? token.arg + this._spaces10 : this._spaces10 + token.arg;
  }
  var pad = length - token.arg.length;
  token.arg = (token.rightJustify) ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg;
};

module.exports = function(){
  var args = Array.prototype.slice.call(arguments),
    stream, format;
  if(args[0] instanceof require('stream').Stream){
    stream = args.shift();
  }
  format = args.shift();
  var formatter = new Formatter(format);
  var string = formatter.format.apply(formatter, args);
  if(stream){
    stream.write(string);
  }else{
    return string;
  }
};

module.exports.Formatter = Formatter;
<|endoftext|>


<commit_before>// # Pubsub
// pub-sub functions for team interaction
/*jslint node: true */

'use strict';

var utils = require('./utils'), annotations = require('./annotations.js'), contentLib = require('./content'), searchLib = require('./search.js');

var faye = require('faye'), fs = require('fs');
var fayeClient;

exports.start = start;
exports.publish = publish;

// Validates client subscriptions against session / system IDs.
function clientSubscribe(loc, cb) {
  fayeClient.subscribe(loc, function(data) {
    var user = GLOBAL.svc.auth.userByClientID(data.clientID), clientID;
    if (user) {
      clientID = data.clientID;
      delete data.clientID;
      cb(data, clientID, user.username);
    } else {
      console.log('invalid client', data);
    }
  });
}

function start(server) {
  var bayeux = new faye.NodeAdapter({mount: GLOBAL.config.FAYEMOUNT, timeout: 45});
  bayeux.attach(server);
  fayeClient = new faye.Client(GLOBAL.config.FAYEHOST);

// user functions

// An item has been created or updated.
//
// Desc contains additional content data.
  clientSubscribe('/itemIndexed', function(combo) {
    requestAnnotate(combo.desc);
    updateItem(combo.cItem);
  });

  clientSubscribe('/team/list', function(data, clientID) {
    GLOBAL.info('team/list'.yellow, data);
    fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
  });

  clientSubscribe('/team/remove', function(data, clientID) {
    GLOBAL.info('team/remove'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users.splice(i, 1);
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/save', function(data, clientID) {
    GLOBAL.info('team/save'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users[i] = data;
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/add', function(data, clientID) {
    GLOBAL.info('team/add'.yellow, data);
    if (data.name && data.type) {
      saveUsers(function() {
        GLOBAL.config.users.push({id : new Date().getTime(), active: new Date(), username : data.name, type: data.type});
      });
      fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
    }
  });

// FIXME log signouts
  clientSubscribe('/logout', function(data) {
    console.log('/logout', data);
  });

// annotations

// retrieve annotations for an individual uri
  clientSubscribe('/annotate', function(data, clientID) {
    GLOBAL.info('/annotate'.yellow, data);
    // FIXME deal with anchors
    GLOBAL.svc.indexer.retrieveByURI(data.uri.replace(/#.*$/, ''), function(err, res) {
      if (err) {
        GLOBAL.error('/annotate error' + err);
      } else {
        fayeClient.publish('/annotations/' + clientID, { uri: data.uri, annotations: res._source.annotations});
      }
    });
  });

  // index incoming annotations, adding validated state
  clientSubscribe('/saveAnnotations', function(combo, clientID, username) {
    GLOBAL.info('saveAnnotations'.yellow, username.blue, combo.uris, combo.annotations.length);
    var user = GLOBAL.svc.auth.getUserByUsername(username);
    try {
      combo.uris.forEach(function(uri) {
        var annos = [];
    // apply validation state based on user
        combo.annotations.forEach(function(p) {
          p.annotatedBy = username;
          p.state = utils.states.annotations.unvalidated;
          if (!user.needsValidation) {
            p.state = utils.states.annotations.validated;
          }
          p.hasTarget = uri;
          var a = annotations.createAnnotation(p);
          annos.push(a);
        });

        contentLib.saveAnnotations(uri, { member: username }, annos, function(err, res, cItem) {
          if (cItem) {
            fayeClient.publish('/annotations/' + clientID, { uri: uri, annotationSummary: cItem.annotationSummary, annotations: cItem.annotations});
          } else {
            utils.passingError('missing cItem');
          }
        });
      });
    } catch (err) {
      console.log('saveAnnotations failed', err);
      console.trace();
    }
  });

  // index subscriptions
  clientSubscribe('/subscriptions/save', function(combo, clientID, username) {
    GLOBAL.info('/subscriptions/save'.yellow, combo);
    combo.matches.forEach(function(match) {
      var sub = {member: username, match: match.trim(), created: new Date()};
      GLOBAL.svc.indexer.saveSubscription(sub);
    });
  });

  // index subscriptions
  clientSubscribe('/subscriptions/retrieve', function(combo, clientID, username) {
    GLOBAL.info('/subscriptions/retrieve'.yellow, combo);
    GLOBAL.svc.indexer.retrieveSubscriptions(username, function(err, results) {
      fayeClient.publish('/subscriptions/results/' + clientID, results);
    }, combo.options);
  });

  // form query
  clientSubscribe('/query', function(data, clientID) {
    GLOBAL.info('/query'.yellow, clientID);
    var query = GLOBAL.svc.indexer.formQuery;
    if (data.nav === 'cluster') {
      query = GLOBAL.svc.indexer.formCluster;
    }
    query(data, function(err, results) {
      if (err) {
        GLOBAL.error('/query query failed', JSON.stringify((results || {}).query, null, 2));
        return;
      }
      if (results.hits) {
        results.nav = data.nav;
        // create annotation overview
        if (data.annotations) {
          var root = { text : 'Annotations', size: 0, children: [], items: [] };
          // build a size of hierarchical annotations
          results.hits.hits.forEach(function(hit) {
            (hit._source.annotations || []).forEach(function(anno) {
              // for each start at the root
              var last = root;
              // iterate through its annotations
              if (!anno.position) {
                GLOBAL.error('missing anno position', anno);
              } else {
                anno.position.forEach(function(p) {
                  var cur;
                  // find its parent
                  last.children.forEach(function(c) {
                    if (c.text === p) {
                      cur = c;
                      return;
                    }
                  });
                  // or create it
                  if (!cur) {
                    last.children.push({text: p, size: 0, children: [], items: []});
                    cur = last.children[last.children.length - 1];
                  }
                  // increment its instances
                  cur.size += 1;
                  // add uri for item selecting
                  if (cur.items.indexOf(hit._source.uri) < 0) {
                    cur.items.push(hit._source.uri);
                  }
                  // use it as the basis for the cur up
                  last = cur;
                });
              }
              // remove raw annotations
              delete hit._source.annotations;
            });
          });
          results.annotationOverview = root;
        }
        GLOBAL.info('queryResults'.yellow, clientID, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('no query results');
      }
    }, data.annotations ? ({ sourceFields : GLOBAL.svc.indexer.sourceFields.concat(['annotations.' + data.annotations])}) : null);
  });

  // form cluster
  clientSubscribe('/cluster', function(data, clientID) {
    GLOBAL.info('/cluster'.yellow, data);
    GLOBAL.svc.indexer.formCluster(data, function(err, results) {
      if (results) {
        GLOBAL.info('  /clusterResults'.yellow, err, results.hits.hits.length);
        fayeClient.publish('/clusterResults/' + clientID, results);
      } else {
        fayeClient.publish('/clusterResults/' + clientID, []);
        GLOBAL.debug('no cluster results');
      }
    });
  });

  // update content of existing item by uri
  clientSubscribe('/updateContent', function(desc, clientID, username) {
    GLOBAL.info('/updateContent'.yellow, desc.uri);
    contentLib.indexContentItem(desc, {member: username, isHTML: true}, utils.passingError);
  });

  // retrieve an existing content item by uri
  clientSubscribe('/getContentItem', function(uri) {
    GLOBAL.info('/getContentItem'.yellow, uri);
    GLOBAL.svc.indexer.retrieveByURI(uri, function(err, result) {
      fayeClient.publish('/updateItem', result);
    });
  });

// delete a document
  clientSubscribe('/delete', function(cItemURIs) {
    GLOBAL.info('/delete'.yellow, cItemURIs);
    GLOBAL.svc.indexer.deleteContentItems(cItemURIs.selected, function(err, res) {
      if (err) {
        GLOBAL.error('/delete', err, res);
      } else {
        fayeClient.publish('/deletedItem', {_id: res._id});
      }
    });
  });

// query moreLikeThis on document
  clientSubscribe('/moreLikeThis', function(data, clientID) {
    GLOBAL.info('/moreLikeThis'.yellow, data);
    GLOBAL.svc.indexer.moreLikeThis(data.uri, function(err, results) {
      if (results) {
        GLOBAL.info('moreLikeThisResults', err, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('NO RESULTS');
      }
    });
  });

// find existing searches
  clientSubscribe('/search/retrieve', function(data, clientID, username) {
    GLOBAL.info('/search/retrieve'.yellow, data);
    publishRetrievedSearches(username);
  });

// save a search
  clientSubscribe('/search/save', function(search, clientID, username) {
    search.member = username;
    GLOBAL.info('/search/save'.yellow, search);
    GLOBAL.svc.indexer.saveSearch(search, function(err, res) {
      setTimeout(function() { publishRetrievedSearches(username); } , 1000);
    });
  });

// subscribe to query requests
  clientSubscribe('/search/queue', function(data, clientID, username) {
    data.member = username;
    console.log('/search/queue'.yellow, data);
    searchLib.queueSearcher(data);
  });
}

// Publish an arbitary message
function publish(channel, message) {
  fayeClient.publish(channel, message);
}

// updates clients with a content item
function updateItem (cItem) {
  fayeClient.publish('/updateItem', cItem);
}

// requests annotators to annotate. pass plain text or it will be extracted from cItem
function requestAnnotate(cItem) {
  GLOBAL.info('requestAnnotate', cItem.uri, 'sel', cItem.selector, 'content', cItem.content ? cItem.content.length : 'nocontent');
  fayeClient.publish('/requestAnnotate', cItem);
}

function publishRetrievedSearches(username) {
  GLOBAL.svc.indexer.retrieveSearches({member: username}, function(err, results) {
    fayeClient.publish('/search/results', results);
    // this is used for general search watchers (cron service)
    fayeClient.publish('/search/updated', {});
  });
}

function saveUsers(istep) {
  fs.writeFileSync('versions/local-users.' + new Date().getTime() + '.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
  if (istep) { istep(); }
  fs.writeFileSync('local-users.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
}
<commit_msg>catch callback<commit_after>// # Pubsub
// pub-sub functions for team interaction
/*jslint node: true */

'use strict';

var utils = require('./utils'), annotations = require('./annotations.js'), contentLib = require('./content'), searchLib = require('./search.js');

var faye = require('faye'), fs = require('fs');
var fayeClient;

exports.start = start;
exports.publish = publish;

// Validates client subscriptions against session / system IDs.
function clientSubscribe(loc, cb) {
  fayeClient.subscribe(loc, function(data) {
    var user = GLOBAL.svc.auth.userByClientID(data.clientID), clientID;
    if (user) {
      clientID = data.clientID;
      delete data.clientID;
      cb(data, clientID, user.username);
    } else {
      console.log('invalid client', data);
    }
  });
}

function start(server) {
  var bayeux = new faye.NodeAdapter({mount: GLOBAL.config.FAYEMOUNT, timeout: 45});
  bayeux.attach(server);
  fayeClient = new faye.Client(GLOBAL.config.FAYEHOST);

// user functions

// An item has been created or updated.
//
// Desc contains additional content data.
  clientSubscribe('/itemIndexed', function(combo) {
    requestAnnotate(combo.desc);
    updateItem(combo.cItem);
  });

  clientSubscribe('/team/list', function(data, clientID) {
    GLOBAL.info('team/list'.yellow, data);
    fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
  });

  clientSubscribe('/team/remove', function(data, clientID) {
    GLOBAL.info('team/remove'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users.splice(i, 1);
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/save', function(data, clientID) {
    GLOBAL.info('team/save'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users[i] = data;
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/add', function(data, clientID) {
    GLOBAL.info('team/add'.yellow, data);
    if (data.name && data.type) {
      saveUsers(function() {
        GLOBAL.config.users.push({id : new Date().getTime(), active: new Date(), username : data.name, type: data.type});
      });
      fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
    }
  });

// FIXME log signouts
  clientSubscribe('/logout', function(data) {
    console.log('/logout', data);
  });

// annotations

// retrieve annotations for an individual uri
  clientSubscribe('/annotate', function(data, clientID) {
    GLOBAL.info('/annotate'.yellow, data);
    // FIXME deal with anchors
    GLOBAL.svc.indexer.retrieveByURI(data.uri.replace(/#.*$/, ''), function(err, res) {
      if (err) {
        GLOBAL.error('/annotate error' + err);
      } else {
        fayeClient.publish('/annotations/' + clientID, { uri: data.uri, annotations: res._source.annotations});
      }
    });
  });

  // index incoming annotations, adding validated state
  clientSubscribe('/saveAnnotations', function(combo, clientID, username) {
    GLOBAL.info('saveAnnotations'.yellow, username.blue, combo.uris, combo.annotations.length);
    var user = GLOBAL.svc.auth.getUserByUsername(username);
    try {
      combo.uris.forEach(function(uri) {
        var annos = [];
    // apply validation state based on user
        combo.annotations.forEach(function(p) {
          p.annotatedBy = username;
          p.state = utils.states.annotations.unvalidated;
          if (!user.needsValidation) {
            p.state = utils.states.annotations.validated;
          }
          p.hasTarget = uri;
          var a = annotations.createAnnotation(p);
          annos.push(a);
        });

        contentLib.saveAnnotations(uri, { member: username }, annos, function(err, res, cItem) {
          if (cItem) {
            fayeClient.publish('/annotations/' + clientID, { uri: uri, annotationSummary: cItem.annotationSummary, annotations: cItem.annotations});
          } else {
            utils.passingError('missing cItem');
          }
        });
      });
    } catch (err) {
      console.log('saveAnnotations failed', err);
      console.trace();
    }
  });

  // index subscriptions
  clientSubscribe('/subscriptions/save', function(combo, clientID, username) {
    GLOBAL.info('/subscriptions/save'.yellow, combo);
    combo.matches.forEach(function(match) {
      var sub = {member: username, match: match.trim(), created: new Date()};
      GLOBAL.svc.indexer.saveSubscription(sub, utils.passingError);
    });
  });

  // index subscriptions
  clientSubscribe('/subscriptions/retrieve', function(combo, clientID, username) {
    GLOBAL.info('/subscriptions/retrieve'.yellow, combo);
    GLOBAL.svc.indexer.retrieveSubscriptions(username, function(err, results) {
      fayeClient.publish('/subscriptions/results/' + clientID, results);
    }, combo.options);
  });

  // form query
  clientSubscribe('/query', function(data, clientID) {
    GLOBAL.info('/query'.yellow, clientID);
    var query = GLOBAL.svc.indexer.formQuery;
    if (data.nav === 'cluster') {
      query = GLOBAL.svc.indexer.formCluster;
    }
    query(data, function(err, results) {
      if (err) {
        GLOBAL.error('/query query failed', JSON.stringify((results || {}).query, null, 2));
        return;
      }
      if (results.hits) {
        results.nav = data.nav;
        // create annotation overview
        if (data.annotations) {
          var root = { text : 'Annotations', size: 0, children: [], items: [] };
          // build a size of hierarchical annotations
          results.hits.hits.forEach(function(hit) {
            (hit._source.annotations || []).forEach(function(anno) {
              // for each start at the root
              var last = root;
              // iterate through its annotations
              if (!anno.position) {
                GLOBAL.error('missing anno position', anno);
              } else {
                anno.position.forEach(function(p) {
                  var cur;
                  // find its parent
                  last.children.forEach(function(c) {
                    if (c.text === p) {
                      cur = c;
                      return;
                    }
                  });
                  // or create it
                  if (!cur) {
                    last.children.push({text: p, size: 0, children: [], items: []});
                    cur = last.children[last.children.length - 1];
                  }
                  // increment its instances
                  cur.size += 1;
                  // add uri for item selecting
                  if (cur.items.indexOf(hit._source.uri) < 0) {
                    cur.items.push(hit._source.uri);
                  }
                  // use it as the basis for the cur up
                  last = cur;
                });
              }
              // remove raw annotations
              delete hit._source.annotations;
            });
          });
          results.annotationOverview = root;
        }
        GLOBAL.info('queryResults'.yellow, clientID, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('no query results');
      }
    }, data.annotations ? ({ sourceFields : GLOBAL.svc.indexer.sourceFields.concat(['annotations.' + data.annotations])}) : null);
  });

  // form cluster
  clientSubscribe('/cluster', function(data, clientID) {
    GLOBAL.info('/cluster'.yellow, data);
    GLOBAL.svc.indexer.formCluster(data, function(err, results) {
      if (results) {
        GLOBAL.info('  /clusterResults'.yellow, err, results.hits.hits.length);
        fayeClient.publish('/clusterResults/' + clientID, results);
      } else {
        fayeClient.publish('/clusterResults/' + clientID, []);
        GLOBAL.debug('no cluster results');
      }
    });
  });

  // update content of existing item by uri
  clientSubscribe('/updateContent', function(desc, clientID, username) {
    GLOBAL.info('/updateContent'.yellow, desc.uri);
    contentLib.indexContentItem(desc, {member: username, isHTML: true}, utils.passingError);
  });

  // retrieve an existing content item by uri
  clientSubscribe('/getContentItem', function(uri) {
    GLOBAL.info('/getContentItem'.yellow, uri);
    GLOBAL.svc.indexer.retrieveByURI(uri, function(err, result) {
      fayeClient.publish('/updateItem', result);
    });
  });

// delete a document
  clientSubscribe('/delete', function(cItemURIs) {
    GLOBAL.info('/delete'.yellow, cItemURIs);
    GLOBAL.svc.indexer.deleteContentItems(cItemURIs.selected, function(err, res) {
      if (err) {
        GLOBAL.error('/delete', err, res);
      } else {
        fayeClient.publish('/deletedItem', {_id: res._id});
      }
    });
  });

// query moreLikeThis on document
  clientSubscribe('/moreLikeThis', function(data, clientID) {
    GLOBAL.info('/moreLikeThis'.yellow, data);
    GLOBAL.svc.indexer.moreLikeThis(data.uri, function(err, results) {
      if (results) {
        GLOBAL.info('moreLikeThisResults', err, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('NO RESULTS');
      }
    });
  });

// find existing searches
  clientSubscribe('/search/retrieve', function(data, clientID, username) {
    GLOBAL.info('/search/retrieve'.yellow, data);
    publishRetrievedSearches(username);
  });

// save a search
  clientSubscribe('/search/save', function(search, clientID, username) {
    search.member = username;
    GLOBAL.info('/search/save'.yellow, search);
    GLOBAL.svc.indexer.saveSearch(search, function(err, res) {
      setTimeout(function() { publishRetrievedSearches(username); } , 1000);
    });
  });

// subscribe to query requests
  clientSubscribe('/search/queue', function(data, clientID, username) {
    data.member = username;
    console.log('/search/queue'.yellow, data);
    searchLib.queueSearcher(data);
  });
}

// Publish an arbitary message
function publish(channel, message) {
  fayeClient.publish(channel, message);
}

// updates clients with a content item
function updateItem (cItem) {
  fayeClient.publish('/updateItem', cItem);
}

// requests annotators to annotate. pass plain text or it will be extracted from cItem
function requestAnnotate(cItem) {
  GLOBAL.info('requestAnnotate', cItem.uri, 'sel', cItem.selector, 'content', cItem.content ? cItem.content.length : 'nocontent');
  fayeClient.publish('/requestAnnotate', cItem);
}

function publishRetrievedSearches(username) {
  GLOBAL.svc.indexer.retrieveSearches({member: username}, function(err, results) {
    fayeClient.publish('/search/results', results);
    // this is used for general search watchers (cron service)
    fayeClient.publish('/search/updated', {});
  });
}

function saveUsers(istep) {
  fs.writeFileSync('versions/local-users.' + new Date().getTime() + '.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
  if (istep) { istep(); }
  fs.writeFileSync('local-users.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
}
<|endoftext|>


<commit_before>/*!
 * Pusher JavaScript Library v1.1
 * http://pusherapp.com/
 *
 * Copyright 2010, New Bamboo
 * Released under the MIT licence.
 */
var Pusher = function(application_key, channel){
  var URL = 'ws://' + Pusher.host + '/app/' + application_key + "?channel=" + channel;
  var conn;
  var self = this;
  this.socket_id;

  var connect = function(){
    conn = new WebSocket(URL);
  };

  if (window["WebSocket"]) {
    connect();
  } else {
    // Mock connection object if WebSockets are not available.
    conn = {};
  }

  var callbacks = {};

  var global_callbacks = [];

  this.bind = function(event_name, callback){
    callbacks[event_name] = callbacks[event_name] || [];
    callbacks[event_name].push(callback);
    return this;//chainable
  };

  this.bind_all = function(callback){
    global_callbacks.push(callback);
  };

  // Not currently supported by pusherapp.com
  this.trigger = function(event_name, data){
    var payload = JSON.stringify({'event' : event_name, 'data' : data});
    Pusher.log("Pusher : sending event : " + payload);
    conn.send(payload);
    return this;
  };

  this.send = function(msg){
    return this.trigger('message', msg);
  };

  conn.onmessage = function(evt){
    var params = JSON.parse(evt.data);
    if (params.socket_id && params.socket_id == self.socket_id) return;
    var event_name = params.event;
    var event_data = Pusher.parser(params.data);
    Pusher.log("Pusher : event received : " + event_name +" : "+ event_data);
    dispatch_global_callbacks(event_name, event_data);
    dispatch(event_name, event_data);
  };

  conn.onclose = function(){
    dispatch('close',null);
    if(Pusher.allow_reconnect){
      Pusher.log('Pusher : socket closed : Reconnecting in 5 seconds...');
      setTimeout(function(){
        Pusher.log('Pusher :: reconnecting');
        connect();
      }, 5000);
    }
  };
  conn.onopen = function(){dispatch('open',null);};

  var dispatch = function(event_name, event_data){
    var chain = callbacks[event_name];
    if(typeof chain == 'undefined'){
      Pusher.log('Pusher : No callbacks for '+event_name);
      return;
    }
    for(var i=0;i<chain.length;i++){
      chain[i](event_data);
    }
  };

  var dispatch_global_callbacks = function(event_name, event_data){
    for(var i=0;i<global_callbacks.length;i++){
      global_callbacks[i](event_name, event_data);
    }
  };

  this.bind('connection_established', function(data){
    self.socket_id = data.socket_id;
  });
};

// Pusher defaults

Pusher.host = "ws.pusherapp.com:80";
Pusher.log = function(msg){}; // e.g. function(m){console.log(m)}
Pusher.allow_reconnect = true;
Pusher.parser = function(data) {
  try {
    return JSON.parse(data);
  } catch(e) {
    Pusher.log("Pusher : data attribute not valid JSON - you may wish to implement your own Pusher.parser");
    return data;
  }
};
<commit_msg>Fixed reconnect and refactored completely<commit_after>/*!
 * Pusher JavaScript Library v1.1
 * http://pusherapp.com/
 *
 * Copyright 2010, New Bamboo
 * Released under the MIT licence.
 */
var Pusher = function(application_key, channel) {
  this.url = 'ws://' + Pusher.host + '/app/' + application_key + "?channel=" + channel;
  this.socket_id;
  this.callbacks = {};
  this.global_callbacks = [];
  this.connect();

  var self = this;

  this.bind('connection_established', function(data) {
    self.socket_id = data.socket_id;
  });
};

Pusher.prototype = {
  connect: function() {
    var self = this;

    if (window["WebSocket"]) {
      this.connection = new WebSocket(this.url);
      this.connection.onmessage = function() {
        self.onmessage.apply(self, arguments);
      };
      this.connection.onclose = function() {
        self.onclose.apply(self, arguments);
      };
      this.connection.onopen = function() {
        self.onopen.apply(self, arguments);
      };
    } else {
      // Mock connection object if WebSockets are not available.
      this.connection = {};
    }
  },

  bind: function(event_name, callback) {
    this.callbacks[event_name] = this.callbacks[event_name] || [];
    this.callbacks[event_name].push(callback);
    return this;
  },

  bind_all: function(callback) {
    this.global_callbacks.push(callback);
    return this;
  },

  // Not currently supported by pusherapp.com
  trigger: function(event_name, data) {
    var payload = JSON.stringify({ 'event' : event_name, 'data' : data });
    Pusher.log("Pusher : sending event : " + payload);
    this.connection.send(payload);
    return this;
  },

  onmessage: function(evt) {
    var params = JSON.parse(evt.data);
    if (params.socket_id && params.socket_id == this.socket_id) return;
    var event_name = params.event;
    var event_data = Pusher.parser(params.data);
    Pusher.log("Pusher : event received : " + event_name +" : "+ event_data);
    this.dispatch_global_callbacks(event_name, event_data);
    this.dispatch(event_name, event_data);
  },

  onclose: function() {
    this.dispatch('close', null);

    var self = this;

    if (Pusher.allow_reconnect){
      Pusher.log('Pusher : socket closed : Reconnecting in 5 seconds...');

      setTimeout(function() {
        Pusher.log('Pusher : reconnecting');
        self.connect();
      }, 5000);
    }
  },

  onopen: function() {
    this.dispatch('open', null);
  },

  dispatch: function(event_name, event_data) {
    var callbacks = this.callbacks[event_name];

    if (callbacks) {
      for (var i = 0; i < callbacks.length; i++) {
        callbacks[i](event_data);
      }
    } else {
      Pusher.log('Pusher : No callbacks for ' + event_name);
    }
  },

  dispatch_global_callbacks: function(event_name, event_data) {
    for (var i = 0; i < this.global_callbacks.length; i++) {
      this.global_callbacks[i](event_name, event_data);
    }
  }
};

// Pusher defaults

Pusher.host = "ws.pusherapp.com:80";
Pusher.log = function(msg){}; // e.g. function(m){console.log(m)}
Pusher.allow_reconnect = true;
Pusher.parser = function(data) {
  try {
    return JSON.parse(data);
  } catch(e) {
    Pusher.log("Pusher : data attribute not valid JSON - you may wish to implement your own Pusher.parser");
    return data;
  }
};
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var debug = require('debug')('wpcom:reblog');

/**
 * Reblog methods
 *
 * @param {String} pid post id
 * @param {String} sid site id
 * @param {WPCOM} wpcom
 * @api public
 */

function Reblog(pid, sid, wpcom){
  if (!sid) {
    throw new Error('`side id` is not correctly defined');
  }

  if (!pid) {
    throw new Error('`post id` is not correctly defined');
  }

  if (!(this instanceof Reblog)) return new Reblog(pid, sid, wpcom);

  this.wpcom = wpcom;
  this._pid = pid;
  this._sid = sid;
}

/**
 * Get reblog status for a Post
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.state =
Reblog.prototype.mine = function(query, fn){
  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/mine';
  this.wpcom.sendRequest(path, query, null, fn);
};

/**
 * Reblog a post
 *
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.add = function(body, fn){
  if (body && !body.destination_site_id) {
    return fn(new Error('destination_site_id is not defined'));
  }

  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/new';
  this.wpcom.sendRequest({ path: path, method: 'post' }, null, body, fn);
};

/**
 * Reblog a post to
 * It's almost a alias of Reblogs#add()
 *
 * @param {Number} dest destination
 * @param {String} [note]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.to = function(dest, note, fn){
  if ('function' == typeof note) {
    fn = note;
    note = null;
  }

  this.add({ note: note, destination_site_id: dest }, fn);
};

/**
 * Expose `Reblog` module
 */

module.exports = Reblog;
<commit_msg>reblog: minor jsdoc change<commit_after>
/**
 * Module dependencies.
 */

var debug = require('debug')('wpcom:reblog');

/**
 * Reblog methods
 *
 * @param {String} pid post id
 * @param {String} sid site id
 * @param {WPCOM} wpcom
 * @api public
 */

function Reblog(pid, sid, wpcom){
  if (!sid) {
    throw new Error('`side id` is not correctly defined');
  }

  if (!pid) {
    throw new Error('`post id` is not correctly defined');
  }

  if (!(this instanceof Reblog)) return new Reblog(pid, sid, wpcom);

  this.wpcom = wpcom;
  this._pid = pid;
  this._sid = sid;
}

/**
 * Get your reblog status for a Post
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.state =
Reblog.prototype.mine = function(query, fn){
  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/mine';
  this.wpcom.sendRequest(path, query, null, fn);
};

/**
 * Reblog a post
 *
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.add = function(body, fn){
  if (body && !body.destination_site_id) {
    return fn(new Error('destination_site_id is not defined'));
  }

  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/new';
  this.wpcom.sendRequest({ path: path, method: 'post' }, null, body, fn);
};

/**
 * Reblog a post to
 * It's almost a alias of Reblogs#add()
 *
 * @param {Number} dest destination
 * @param {String} [note]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.to = function(dest, note, fn){
  if ('function' == typeof note) {
    fn = note;
    note = null;
  }

  this.add({ note: note, destination_site_id: dest }, fn);
};

/**
 * Expose `Reblog` module
 */

module.exports = Reblog;
<|endoftext|>


<commit_before>'use strict';

let run = 'run(-script)?';
let scriptName = '\\w|\\:|\\*|$';
let arg = `${scriptName}| --`;
let reserved = '(re)?start|stop|test|publish|(un?)install|version';
let reservedTest = '(te?s)?t';
let redrunArg = '--?\\w+';
let redrunName ='\\w+';
let redrunEnd = ';|&&?|\\|\\|?|>|<| #';

module.exports.enter        = RegExp(`redrun|(npm (${run}|${reserved}|${reservedTest}))`);
module.exports.arg          = RegExp(`npm ${run} (${arg})+`, 'g');
module.exports.name         = RegExp(`(${scriptName})+`);
module.exports.reserved     = RegExp(`npm (${reserved})`);
module.exports.reservedTest = RegExp(`^npm ${reservedTest}$`);
module.exports.redrun       = RegExp(`^redrun(\\s|${redrunArg}|${redrunName})+?(${redrunEnd})?$`);

<commit_msg>chore(regexp) add " "<commit_after>'use strict';

let run = 'run(-script)?';
let scriptName = '\\w|\\:|\\*|$';
let arg = `${scriptName}| --`;
let reserved = '(re)?start|stop|test|publish|(un?)install|version';
let reservedTest = '(te?s)?t';
let redrunArg = '--?\\w+';
let redrunName = '\\w+';
let redrunEnd = ';|&&?|\\|\\|?|>|<| #';

module.exports.enter        = RegExp(`redrun|(npm (${run}|${reserved}|${reservedTest}))`);
module.exports.arg          = RegExp(`npm ${run} (${arg})+`, 'g');
module.exports.name         = RegExp(`(${scriptName})+`);
module.exports.reserved     = RegExp(`npm (${reserved})`);
module.exports.reservedTest = RegExp(`^npm ${reservedTest}$`);
module.exports.redrun       = RegExp(`^redrun(\\s|${redrunArg}|${redrunName})+?(${redrunEnd})?$`);

<|endoftext|>


<commit_before>const crypto = require('crypto');
const formatString = require('util').format;
const fs = require('fs');
const http = require('http');
const jws = require('jws');
const mime = require('mime');
const request = require('request');
const url = require('url');
const utils = require('./modelUtils');

function makeUrl (endpoint, path, query) {
  var uri = url.resolve(endpoint, path);
  if (query) {
    uri = url.parse(uri, true);
    uri.query = Object.keys(query).reduce(function (q, key) {
      return (q[key] = query[key], q);
    }, uri.query || {});
    delete uri.search;
    uri = url.format(uri);
  }
  return uri;
}

function errorNameFromStatusCode (code) {
  var status = http.STATUS_CODES[parseInt(code, 10)];

  if (!status)
    return null;

  var name = status.split(/\s+/).reduce(function(name, part) {
    name += part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
    return name;
  }, '').replace(/\W+/g, '');

  if (!/Error$/.test(name))
    name += 'Error';

  return name;
}

function fixError (res, body) {
  var errName = body.code || (body.error ? body.error.code : '');
  var errMsg = body.message || (body.error ? body.error.message : '') || '';

  if (errName) {
    if (!/Error$/.test(errName))
      errName += 'Error';
  } else {
    errName = errorNameFromStatusCode(code) || 'Http' + res.statusCode + 'Error';
  }

  var err = new Error(errMsg);
  Object.defineProperties(err, {
    code: {value: res.statusCode},
    name: {value: errName},
    details: {value: body.details}
  });

  return err;
}

function encodeDataItem (key, value) {
  if (key === 'image' && /^[\/\.]/.test(value)) {
    value = {
      path: value,
      type: mime.lookup(value)
    };
  }

  if (value && value.type && (value.path || value.data)) {
    var data = (value.data || fs.readFileSync(value.path)).toString('base64');
    value = formatString('data:%s;base64,%s', value.type, data);
  }

  return value;
}

function signRequest (req, auth) {
  if (typeof auth === 'string')
    auth = {secret: auth};

  var key = auth.key || 'master';
  var secret = auth.secret;

  var data = {
    header: {typ: 'JWT', alg: 'HS256'},
    payload: {
      key: key,
      exp: Date.now() + (1000 * 60),
      method: req.method.toUpperCase(),
      path: url.parse(req.url).path
    },
    secret: secret
  };

  if (req.body) {
    data.payload.body = {
      alg: 'SHA256',
      hash: crypto.createHash('SHA256').update(req.body).digest('hex')
    }
  }

  req.headers['Authorization'] = 'JWT token="' + jws.sign(data) + '"';

  return req;
}

function Remote (endpoint, auth) {
  if (!(this instanceof Remote))
    return new Remote(endpoint, auth);

  function makeCall (method, meta, cb) {
    if (typeof meta === 'string')
      meta = {path: meta};

    var filter = meta.filter;
    var defaultValue = meta.default;
    var generator = meta.generator;

    var config = {
      url: makeUrl(endpoint, meta.path, meta.query),
      method: method,
      headers: {
        'User-Agent': 'BadgeKit API Client',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    };

    if (meta.data) {
      config.body = JSON.stringify(meta.data, encodeDataItem);
    }

    var call = request(signRequest(config, meta.auth || auth), function (err, rsp, body) {
      if (typeof cb !== 'function')
        return;

      if (typeof body === 'string') {
        try {
          body = JSON.parse(body);
        } catch (e) {
          // NO-OP - leave body as is
        }
      }

      if (err) {
        if (err.name === 'Error')
          Object.defineProperty(err, 'name', {value: 'HttpError'});
        return cb(err, null);
      }

      if (rsp.statusCode >= 400) {
        cb(fixError(rsp, body), null);
      } else {
        body = generator.preformat(body);

        if (filter)
          body = (body||{})[filter];

        if (typeof body === 'undefined')
          body = defaultValue;

        if (generator)
          body = generator(body);

        cb(null, body);
      }
    });
  }

  ['get', 'head', 'post', 'put', 'delete'].forEach(function (method) {
    Object.defineProperty(this, method, {
      enumerable: true,
      value: makeCall.bind(this, method)
    });
  }.bind(this));

}

exports = module.exports = Remote;
<commit_msg>Fixing code reference<commit_after>const crypto = require('crypto');
const formatString = require('util').format;
const fs = require('fs');
const http = require('http');
const jws = require('jws');
const mime = require('mime');
const request = require('request');
const url = require('url');
const utils = require('./modelUtils');

function makeUrl (endpoint, path, query) {
  var uri = url.resolve(endpoint, path);
  if (query) {
    uri = url.parse(uri, true);
    uri.query = Object.keys(query).reduce(function (q, key) {
      return (q[key] = query[key], q);
    }, uri.query || {});
    delete uri.search;
    uri = url.format(uri);
  }
  return uri;
}

function errorNameFromStatusCode (code) {
  var status = http.STATUS_CODES[parseInt(code, 10)];

  if (!status)
    return null;

  var name = status.split(/\s+/).reduce(function(name, part) {
    name += part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
    return name;
  }, '').replace(/\W+/g, '');

  if (!/Error$/.test(name))
    name += 'Error';

  return name;
}

function fixError (res, body) {
  var errName = body.code || (body.error ? body.error.code : '');
  var errMsg = body.message || (body.error ? body.error.message : '') || '';

  if (errName) {
    if (!/Error$/.test(errName))
      errName += 'Error';
  } else {
    errName = errorNameFromStatusCode(res.statusCode) || 'Http' + res.statusCode + 'Error';
  }

  var err = new Error(errMsg);
  Object.defineProperties(err, {
    code: {value: res.statusCode},
    name: {value: errName},
    details: {value: body.details}
  });

  return err;
}

function encodeDataItem (key, value) {
  if (key === 'image' && /^[\/\.]/.test(value)) {
    value = {
      path: value,
      type: mime.lookup(value)
    };
  }

  if (value && value.type && (value.path || value.data)) {
    var data = (value.data || fs.readFileSync(value.path)).toString('base64');
    value = formatString('data:%s;base64,%s', value.type, data);
  }

  return value;
}

function signRequest (req, auth) {
  if (typeof auth === 'string')
    auth = {secret: auth};

  var key = auth.key || 'master';
  var secret = auth.secret;

  var data = {
    header: {typ: 'JWT', alg: 'HS256'},
    payload: {
      key: key,
      exp: Date.now() + (1000 * 60),
      method: req.method.toUpperCase(),
      path: url.parse(req.url).path
    },
    secret: secret
  };

  if (req.body) {
    data.payload.body = {
      alg: 'SHA256',
      hash: crypto.createHash('SHA256').update(req.body).digest('hex')
    }
  }

  req.headers['Authorization'] = 'JWT token="' + jws.sign(data) + '"';

  return req;
}

function Remote (endpoint, auth) {
  if (!(this instanceof Remote))
    return new Remote(endpoint, auth);

  function makeCall (method, meta, cb) {
    if (typeof meta === 'string')
      meta = {path: meta};

    var filter = meta.filter;
    var defaultValue = meta.default;
    var generator = meta.generator;

    var config = {
      url: makeUrl(endpoint, meta.path, meta.query),
      method: method,
      headers: {
        'User-Agent': 'BadgeKit API Client',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    };

    if (meta.data) {
      config.body = JSON.stringify(meta.data, encodeDataItem);
    }

    var call = request(signRequest(config, meta.auth || auth), function (err, rsp, body) {
      if (typeof cb !== 'function')
        return;

      if (typeof body === 'string') {
        try {
          body = JSON.parse(body);
        } catch (e) {
          // NO-OP - leave body as is
        }
      }

      if (err) {
        if (err.name === 'Error')
          Object.defineProperty(err, 'name', {value: 'HttpError'});
        return cb(err, null);
      }

      if (rsp.statusCode >= 400) {
        cb(fixError(rsp, body), null);
      } else {
        body = generator.preformat(body);

        if (filter)
          body = (body||{})[filter];

        if (typeof body === 'undefined')
          body = defaultValue;

        if (generator)
          body = generator(body);

        cb(null, body);
      }
    });
  }

  ['get', 'head', 'post', 'put', 'delete'].forEach(function (method) {
    Object.defineProperty(this, method, {
      enumerable: true,
      value: makeCall.bind(this, method)
    });
  }.bind(this));

}

exports = module.exports = Remote;
<|endoftext|>


<commit_before>var template = require("lodash.template");
var gutil = require("gulp-util");
var api = require("./extra_api");
var extend = require("node.extend");
var path = require("path");

"use strict";

var defaults = {
  error: {
    icon: path.join(__dirname, '..', 'assets', 'gulp-error.png'),
    sound: 'Frog'
  },
  regular: {
    icon: path.join(__dirname, '..', 'assets', 'gulp.png')
  }
};

module.exports = function (reporter, message, options, templateOptions, callback) {
  var self = this;
  callback = callback || function () {};
  if (!reporter) return callback(new gutil.PluginError("gulp-notify", "No reporter specified."));

  // Try/catch the only way to go to ensure catching all errors? Domains?
  try {
    var options = constructOptions(options, message, templateOptions);
    if (!options) {
      return callback();
    }
    api.logError(options, (message instanceof Error));
    reporter(options, function (err) {
      if (err) return callback(new gutil.PluginError("gulp-notify", err));
      return callback();
    });
  } catch (err) {
    return callback(new gutil.PluginError("gulp-notify", err));
  }
};

function generate (outputData, object, title, message, templateOptions) {
  if (object instanceof Error) {
    var titleTemplate = template(title);
    var messageTemplate = template(message);

    return extend(defaults.error, outputData, {
      title: titleTemplate({
        error: object,
        options: templateOptions
      }),
      message: messageTemplate({
        error: object,
        options: templateOptions
      })
    });
  }

  return extend(defaults.regular, outputData, {
    title: gutil.template(title, {
      file: object,
      options: templateOptions
    }),
    message: gutil.template(message, {
      file: object,
      options: templateOptions
    })
  });
}

function constructOptions (options, object, templateOptions) {
  var message = object.path || object.message || object,
      title = !(object instanceof Error) ? "Gulp notification" : "Error running Gulp",
      outputData = {};

  if (typeof options === "function") {
    message = options(object);
    if (!message) {
      return false;
    }
  }

  if (typeof options === "string") {
    message = options;
  }

  if (typeof options === "object") {
    outputData = extend(true, {}, options);
    if (typeof outputData.title === "function") {
      title = outputData.title(object);
    } else {
      title = outputData.title || title;
    }

    if (typeof outputData.message === "function") {
      message = outputData.message(object);
      if (!message) {
        return false;
      }
    } else {
      message = outputData.message || message;
    }
  }
  return generate(outputData, object, title, message, templateOptions);
}
<commit_msg>add templating to subtitle and open<commit_after>var template = require("lodash.template");
var gutil = require("gulp-util");
var api = require("./extra_api");
var extend = require("node.extend");
var path = require("path");

"use strict";

var defaults = {
  error: {
    icon: path.join(__dirname, '..', 'assets', 'gulp-error.png'),
    sound: 'Frog'
  },
  regular: {
    icon: path.join(__dirname, '..', 'assets', 'gulp.png')
  }
};

module.exports = function (reporter, message, options, templateOptions, callback) {
  var self = this;
  callback = callback || function () {};
  if (!reporter) return callback(new gutil.PluginError("gulp-notify", "No reporter specified."));

  // Try/catch the only way to go to ensure catching all errors? Domains?
  try {
    var options = constructOptions(options, message, templateOptions);
    if (!options) {
      return callback();
    }
    api.logError(options, (message instanceof Error));
    reporter(options, function (err) {
      if (err) return callback(new gutil.PluginError("gulp-notify", err));
      return callback();
    });
  } catch (err) {
    return callback(new gutil.PluginError("gulp-notify", err));
  }
};

function generate (outputData, object, title, message, subtitle, open, templateOptions) {
  if (object instanceof Error) {
    var titleTemplate = template(title);
    var messageTemplate = template(message);
    var openTemplate = template(open);
    var subtitleTemplate = template(subtitle);

    return extend(defaults.error, outputData, {
      title: titleTemplate({
        error: object,
        options: templateOptions
      }),
      message: messageTemplate({
        error: object,
        options: templateOptions
      }),
      open: openTemplate({
        error: object,
        options: templateOptions
      }),
      subtitle: subtitleTemplate({
        error: object,
        options: templateOptions
      })
    });
  }

  return extend(defaults.regular, outputData, {
    title: gutil.template(title, {
      file: object,
      options: templateOptions
    }),
    message: gutil.template(message, {
      file: object,
      options: templateOptions
    })
  });
}

function constructOptions (options, object, templateOptions) {
  var message = object.path || object.message || object,
      title = !(object instanceof Error) ? "Gulp notification" : "Error running Gulp",
      open = "",
      subtitle = "",
      outputData = {};

  if (typeof options === "function") {
    message = options(object);
    if (!message) {
      return false;
    }
  }

  if (typeof options === "string") {
    message = options;
  }

  if (typeof options === "object") {
    outputData = extend(true, {}, options);
    if (typeof outputData.title === "function") {
      title = outputData.title(object);
    } else {
      title = outputData.title || title;
    }

    if (typeof outputData.subtitle === "function") {
      subtitle = outputData.subtitle(object);
    } else {
      subtitle = outputData.subtitle || subtitle;
    }

    if (typeof outputData.open === "function") {
      open = outputData.open(object);
    } else {
      open = outputData.open || open;
    }

    if (typeof outputData.message === "function") {
      message = outputData.message(object);
      if (!message) {
        return false;
      }
    } else {
      message = outputData.message || message;
    }
  }
  return generate(outputData, object, title, message, subtitle, open, templateOptions);
}
<|endoftext|>


<commit_before>"use strict";

/**
 * @author EmmanuelOlaojo
 * @since 7/31/16
 */

var Promise = require("bluebird");
var constants = require("./constants");

var INITIAL = constants.INITIAL;
var DONE = constants.DONE;
var SAVE = constants.SAVE;
var UPDATE = constants.UPDATE;
var REMOVE = constants.REMOVE;
var ROLLED = constants.ROLLED;
var TaskMdl;
var utils;
var updateState;
var getModel;

var RollerProvider = function(mongoose, _TaskMdl){
  TaskMdl = _TaskMdl;
  utils = require("./utils")(mongoose);
  updateState = utils.updateState;
  getModel = utils.getModel;

  return Roller;
};

var Roller = {
  roll: function(){
    var chain = Promise.resolve();

    return TaskMdl.find().exec()
      .then(function(tasks){

        tasks.forEach(function(task){
          chain = chain.then(function(){
            return rollBackTask(task);
          });
        });

        return chain;
      });
  }

  , rollOne: rollBackTask
};

function rollBackTask(task) {
  var chain = Promise.resolve();
  var step;
  var lastIndex;
  var firstStep;
  var lastStep;

  lastIndex = task.steps.length - 1;
  firstStep = task.steps[0];
  lastStep = task.steps[lastIndex];

  if(lastStep.state !== DONE && firstStep.state !== INITIAL){
    for(var i = lastIndex; i >= 0 ; i--){
      step = task.steps[i];

      if(step.state === INITIAL || step.state === ROLLED) continue;

      //iife to avoid async issues
      (function(step){
        chain = chain.then(function(){
          return getRollbackFunc(step)(step, task);
        });
      })(step);
    }
  }

  return chain.then(function(){
    return task.remove();
  });
}

function getRollbackFunc(step){
  switch(step.type){
    case SAVE: return rollbackSave;
    case UPDATE:
    case REMOVE: return rollbackDeleteOrUpdate
  }
}

function rollbackSave(save, task) {
  var Collection = getModel(save.name);

  return Collection.remove({_id: save.dataStore[0]._id})
    .exec()
    .then(function(){
      return updateState(task, save.index, ROLLED);
    });
}

function rollbackDeleteOrUpdate(step, task){
  var Collection = getModel(step.name);
  var chain = Promise.resolve();

  step.dataStore.forEach(function(data){
    chain = chain.then(function(){
      return Collection.findById(data._id)
        .exec()
        .then(function(doc){
          if(doc) {
            return doc.update(data).exec();
          }

          doc = new Collection(data);
          return doc.save();
        });
    });
  });

  return chain.then(function(){
    return updateState(task, step.index, ROLLED);
  });
}

module.exports = RollerProvider;
<commit_msg>added cautionary comment to rollOne<commit_after>"use strict";

/**
 * @author EmmanuelOlaojo
 * @since 7/31/16
 */

var Promise = require("bluebird");
var constants = require("./constants");

var INITIAL = constants.INITIAL;
var DONE = constants.DONE;
var SAVE = constants.SAVE;
var UPDATE = constants.UPDATE;
var REMOVE = constants.REMOVE;
var ROLLED = constants.ROLLED;
var TaskMdl;
var utils;
var updateState;
var getModel;

var RollerProvider = function(mongoose, _TaskMdl){
  TaskMdl = _TaskMdl;
  utils = require("./utils")(mongoose);
  updateState = utils.updateState;
  getModel = utils.getModel;

  return Roller;
};

var Roller = {
  roll: function(){
    var chain = Promise.resolve();

    return TaskMdl.find().exec()
      .then(function(tasks){

        tasks.forEach(function(task){
          chain = chain.then(function(){
            return rollBackTask(task);
          });
        });

        return chain;
      });
  }

  // for internal use only
  , rollOne: rollBackTask
};

function rollBackTask(task) {
  var chain = Promise.resolve();
  var step;
  var lastIndex;
  var firstStep;
  var lastStep;

  lastIndex = task.steps.length - 1;
  firstStep = task.steps[0];
  lastStep = task.steps[lastIndex];

  if(lastStep.state !== DONE && firstStep.state !== INITIAL){
    for(var i = lastIndex; i >= 0 ; i--){
      step = task.steps[i];

      if(step.state === INITIAL || step.state === ROLLED) continue;

      //iife to avoid async issues
      (function(step){
        chain = chain.then(function(){
          return getRollbackFunc(step)(step, task);
        });
      })(step);
    }
  }

  return chain.then(function(){
    return task.remove();
  });
}

function getRollbackFunc(step){
  switch(step.type){
    case SAVE: return rollbackSave;
    case UPDATE:
    case REMOVE: return rollbackDeleteOrUpdate
  }
}

function rollbackSave(save, task) {
  var Collection = getModel(save.name);

  return Collection.remove({_id: save.dataStore[0]._id})
    .exec()
    .then(function(){
      return updateState(task, save.index, ROLLED);
    });
}

function rollbackDeleteOrUpdate(step, task){
  var Collection = getModel(step.name);
  var chain = Promise.resolve();

  step.dataStore.forEach(function(data){
    chain = chain.then(function(){
      return Collection.findById(data._id)
        .exec()
        .then(function(doc){
          if(doc) {
            return doc.update(data).exec();
          }

          doc = new Collection(data);
          return doc.save();
        });
    });
  });

  return chain.then(function(){
    return updateState(task, step.index, ROLLED);
  });
}

module.exports = RollerProvider;
<|endoftext|>


<commit_before>var http          = require("http"),
  fs              = require("fs"),
  Connect         = require("connect"),
  cache           = require("./simple-cache"),
  EventPublisher  = require("./event-publisher"),
  parser          = require("./parser"),
  _               = require("underscore")._,
  connectRouter   = require("./router_connect"),
  querystring     = require("querystring"),
  ajaxProxy       = require("./ajaxProxy"),
  Rest            = require("./rest"),
  nodePath        = require("path"),
  send            = require("send"),
  Semaphore       = require("./semaphore");

/**
 * @name Router
 * @class Provides the router for the feather framework
 */

/*
 * Respond with 403 "Forbidden".
 */
function forbidden(res) {
  var body = 'Forbidden';
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Content-Length', body.length);
  res.statusCode = 403;
  res.end(body);
}

/*
 * Respond with 404 "Not Found".
 */
function notFound(res) {
  var body = 'Not Found';
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Content-Length', body.length);
  res.statusCode = 404;
  res.end(body);
}

/**
 * @memberOf Router
 * @function
 * @param {Object} options
 * @param {Function} cb called upon completion.  The function is passed an error (or null), and a Connect.router instance.
 */
var getRouter = exports.getRouter = function(options, cb) {
  cache.getItemsWait([
    "feather-resourcePublishers",
    "feather-logger",
    "feather-files",
    "feather-options",
    "feather-domPool"
  ], function(err, cacheItems) {
    if (err) cb(err); else {
      var appOptions = cacheItems["feather-options"],
        logger = cacheItems["feather-logger"],
        resourceCaches = cacheItems["feather-resourcePublishers"],
        files = cacheItems["feather-files"],
        domPool = cacheItems["feather-domPool"];

      //cache auth rules RegExp instances
      if (appOptions.auth.rules) {
        for (var expression in appOptions.auth.rules) {
          var authRule = appOptions.auth.rules[expression];
          authRule.regex = new RegExp(expression);
        }
      }

      var router = connectRouter(function(app) {
        //special-case routes first (mainly used for white-listing the serving of resources that live outside of /public)
        app.get(/^\/(feather-client)\/(.*)/, function(req, res, next) {
          var _path = nodePath.join(options.featherRoot, req.params[0], req.params[1]);
          send(req, _path).pipe(res);
        });

        //if the app is configured to use AJAX instead of socket.io for system requests, hook up that route
        if (appOptions.useAjaxForSystem) {
          app.post("/_ajax/", ajaxProxy);
        }

        // set up the consumable rest api.  This will get simple-cached and get sucked into feather.rest when the server init calls back (successfully).
        // Should we do it this way, or set up a separate router for dynamic rest calls that takes priority over this one?
        var rest = new Rest(app);

        //if any REST apis are defined for this app, include those routes here
        if (files.restFiles && files.restFiles.length > 0) {
          _.each(files.restFiles, function(fileName) {
            var base = nodePath.basename(fileName, '.js');
            var restDocPath = "";
            var api = require(nodePath.join(appOptions.appRoot, "rest", fileName));
            _.each(_.keys(api), function(verb) {
              _.each(_.keys(api[verb]), function(route) {
                rest.registerRoute(verb, base + route, api[verb][route], function(err) {
                  if (err) throw new Error(err);
                });
              });
            });
          });
        }

        /**
         * non-special-case routes (.feather.html or static resources that live in /public)
         * @param {Object} req
         * @param {Object} res
         * @param {Object} next
         */

        var handleFeatherReq = function(req, res, next) {
          var redirect = false;
          //first, apply auth rules to the url
          if (appOptions.auth.rules) {
            for (var expression in appOptions.auth.rules) {
              var authRule = appOptions.auth.rules[expression];
              if (req.url.match(authRule.regex)) {
                if (authRule.loginRequired && authRule.redirectUrl) {
                  //TODO: allow role checking here as well
                  if (!req.session || !req.session.user) {
                    //if we _do_ have a session but just no user... store requested url for redirection after auth takes place
                    if (req.session) {
                      req.session.requestedUrl = req.url;
                    }
                    req.url = authRule.redirectUrl;
                    redirect = true;
                    next();
                    break;
                  }
                }
              }
            }
          }

          if (!redirect) {
            //blacklisting for widget .server.js files
            if (req.url.indexOf(".server.js") > -1) { //NOTE: req.page gets set in middleware prior to this router
              return forbidden(res);
            }

            if (req.isFeatherPage) {
              //TODO: for now, hardcoding the feather-res-cache path prefix, but at some point we probably want to make that configurable...
              var publishedPath = nodePath.join(options.publicRoot, "feather-res-cache", req.page);
              send(req, publishedPath).pipe(res);
            } else {
              //no matching feather resource found, move on to the next middleware in the stack
              next();
            }
          }
        };

        app.get(/\/(.*)/, handleFeatherReq);

        app.post(/\/(.*)/, handleFeatherReq);

        //escape the stack with the router and its rest handler if present
        process.nextTick(function() {
          cb(null, router, rest);
        });
      });
    }
  });
};
<commit_msg>support app-level .authorize() function from feather's auth sub-middleware layer; this makes is a little nicer to share auth rules from feather to the app layer;<commit_after>var http          = require("http"),
  fs              = require("fs"),
  Connect         = require("connect"),
  cache           = require("./simple-cache"),
  EventPublisher  = require("./event-publisher"),
  parser          = require("./parser"),
  _               = require("underscore")._,
  connectRouter   = require("./router_connect"),
  querystring     = require("querystring"),
  ajaxProxy       = require("./ajaxProxy"),
  Rest            = require("./rest"),
  nodePath        = require("path"),
  send            = require("send"),
  Semaphore       = require("./semaphore");

/**
 * @name Router
 * @class Provides the router for the feather framework
 */

/*
 * Respond with 401 "Unauthorized".
 */
function unauthorized(res) {
  var body = 'Unauthorized';
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Content-Length', body.length);
  res.statusCode = 401;
  res.end(body);
}

/*
 * Respond with 403 "Forbidden".
 */
function forbidden(res) {
  var body = 'Forbidden';
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Content-Length', body.length);
  res.statusCode = 403;
  res.end(body);
}

/*
 * Respond with 404 "Not Found".
 */
function notFound(res) {
  var body = 'Not Found';
  res.setHeader('Content-Type', 'text/plain');
  res.setHeader('Content-Length', body.length);
  res.statusCode = 404;
  res.end(body);
}

//if the app doesn't define a custom authorize function for routes, use this one to authorize all routes
function noopAuthorize(req, res, authorized, _unauthorized) {
  authorized();
};

/**
 * @memberOf Router
 * @function
 * @param {Object} options
 * @param {Function} cb called upon completion.  The function is passed an error (or null), and a Connect.router instance.
 */
var getRouter = exports.getRouter = function(options, cb) {
  cache.getItemsWait([
    "feather-resourcePublishers",
    "feather-logger",
    "feather-files",
    "feather-options",
    "feather-domPool"
  ], function(err, cacheItems) {
    if (err) cb(err); else {
      var appOptions = cacheItems["feather-options"],
        logger = cacheItems["feather-logger"],
        resourceCaches = cacheItems["feather-resourcePublishers"],
        files = cacheItems["feather-files"],
        domPool = cacheItems["feather-domPool"];

      //normalize authorize function
      options.authorize = options.authorize || noopAuthorize;

      //cache auth rules RegExp instances
      if (appOptions.auth.rules) {
        for (var expression in appOptions.auth.rules) {
          var authRule = appOptions.auth.rules[expression];
          authRule.regex = new RegExp(expression);
          authRule.expression = expression;
        }
      }

      var router = connectRouter(function(app) {
        //special-case routes first (mainly used for white-listing the serving of resources that live outside of /public)
        app.get(/^\/(feather-client)\/(.*)/, function(req, res, next) {
          var _path = nodePath.join(options.featherRoot, req.params[0], req.params[1]);
          send(req, _path).pipe(res);
        });

        //if the app is configured to use AJAX instead of socket.io for system requests, hook up that route
        if (appOptions.useAjaxForSystem) {
          app.post("/_ajax/", ajaxProxy);
        }

        // set up the consumable rest api.  This will get simple-cached and get sucked into feather.rest when the server init calls back (successfully).
        // Should we do it this way, or set up a separate router for dynamic rest calls that takes priority over this one?
        var rest = new Rest(app);

        //if any REST apis are defined for this app, include those routes here
        if (files.restFiles && files.restFiles.length > 0) {
          _.each(files.restFiles, function(fileName) {
            var base = nodePath.basename(fileName, '.js');
            var restDocPath = "";
            var api = require(nodePath.join(appOptions.appRoot, "rest", fileName));
            _.each(_.keys(api), function(verb) {
              _.each(_.keys(api[verb]), function(route) {
                rest.registerRoute(verb, base + route, api[verb][route], function(err) {
                  if (err) throw new Error(err);
                });
              });
            });
          });
        }

        /**
         * non-special-case routes (.feather.html or static resources that live in /public)
         * @param {Object} req
         * @param {Object} res
         * @param {Object} next
         */

        var handleFeatherReq = function(req, res, next) {

          var completeRequest = function() {
            //if we got here, auth&auth passed...

            //blacklisting for widget .server.js files
            if (req.url.indexOf(".server.js") > -1) { //NOTE: req.page gets set in middleware prior to this router
              return forbidden(res);
            }

            if (req.isFeatherPage) {
              //TODO: for now, hardcoding the feather-res-cache path prefix, but at some point we probably want to make that configurable...
              var publishedPath = nodePath.join(options.publicRoot, "feather-res-cache", req.page);
              send(req, publishedPath).pipe(res);
            } else {
              //no matching feather resource found, move on to the next middleware in the stack
              next();
            }
          };

          //first, apply auth rules to the url
          if (appOptions.auth.rules) {
            for (var expression in appOptions.auth.rules) {
              var authRule = appOptions.auth.rules[expression];

              if (req.url.match(authRule.regex)) {
                if (authRule.loginRequired) {
                  //TODO: allow role checking here as well
                  if (!req.session || !req.session.user) {
                    if (authRule.redirectUrl) {
                      //if we _do_ have a session but just no user... store requested url for redirection after auth takes place
                      if (req.session) {
                        req.session.requestedUrl = req.url;
                      }
                      req.url = authRule.redirectUrl;
                      return next();
                    } else {
                      return unauthorized(res);
                    }
                  }

                  //authentication passed, now allow app to authorize the request
                  req.authRule = authRule;
                  return options.authorize(req, res,
                    completeRequest,

                    //the error/unauthorized case...
                    function(err) {
                      logger.warn({category: 'auth', message: 'unauthorized access attempt; url: ' + req.url + '; app auth error message: ' + (err || 'no_error_message_provided')});

                      if (authRule.redirectUrlUnauthorized) {
                        //send the user back to redirectUrlUnauthorized
                        req.url = authRule.redirectUrlUnauthorized;
                        delete req.isFeatherPage;
                        return next();
                      }

                      //no app configured redirect URL, just return a 403
                      return forbidden(res);
                    }
                  );
                }
              }
            }

            //no matching rule found, just complete the request
            completeRequest();
          } else {
            completeRequest();
          }
        };

        app.get(/\/(.*)/, handleFeatherReq);

        app.post(/\/(.*)/, handleFeatherReq);

        //escape the stack with the router and its rest handler if present
        process.nextTick(function() {
          cb(null, router, rest);
        });
      });
    }
  });
};
<|endoftext|>