<commit_before>var utils = require('../utils');

module.exports = function Bound () {

  function traverse (obj, key, cb, thisArg) {
    var value;
    for (var name in obj) {
      value = obj[name];
      var newKey = key ? key + '.' + name : name;
      if ('object' === typeof value) {
        traverse(value, newKey, cb, thisArg);
      } else {
        cb.call(thisArg, newKey, value);
      }
    }
  }

  this.defaults = utils.defaults({}, this.defaults, {
    data: null,
    binding: null
  });

  this.update = function (obj) {
    if (obj) {
      var oldData = this.data;
      this.data = obj;
      var e = utils.createEvent('datachange', { oldValue: oldData, newValue: this.data });
      this.el.dispatchEvent(e);
    }
    traverse(this.data, '', this.setDOMValue, this);
  };

  this.readInput = function () {
    if (this.binding) {
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var nodes, key = binding.key;
        if (selector[0] === '@') {
          nodes = this.ui[selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(selector));
        }
        nodes.forEach(function (node) {
          if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            var value;
            if (node.type.toLowerCase() === 'checkbox') {
              value = node.checked;
            } else if (node.type.toLowerCase() === 'radio') {
              value = 'undefined' === typeof node.value ? 1 : node.value;
              value = node.checked ? value : undefined;
            } else {
              value = node.value;
            }
            this.set(key, value);
          }
        }.bind(this));
      }, this);
    }
  };

  this.reset = function () {
    if (this.binding) {
      this._keyBinding = this._keyBinding || {};
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var key = binding.key;
        this.setDOMValue(key, null);
      }, this);
    }
  };

  this.set = function (key, value) {
    if (!utils.isString(key)) {
      traverse(key, '', this.set, this);
    } else {
      this.setDataValue(key, value);
      this.setDOMValue(key, value);
    }
  };

  this.get = function (key) {
    return utils.getObjectPath(this.data, key);
  };

  this.bind = function () {
    if (this.binding) {
      this._keyBinding = this._keyBinding || {};
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var nodes, key = binding.key, events = binding.events || ['change'];
        if (selector[0] === '@') {
          nodes = this.ui[selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(selector));
        }
        nodes.forEach(function (node) {
          if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            utils.forIn(events, function (eventName) {
              this.listenTo(node, eventName + ':bound', function (event) {
                var value, el = event.target;
                if (el.type.toLowerCase() === 'checkbox') {
                  value = el.checked;
                } else if (el.type.toLowerCase() === 'radio') {
                  value = 'undefined' === typeof el.value ? 1 : el.value;
                  value = el.checked ? value : undefined;
                } else {
                  value = el.value;
                }
                this.set(key, value);
              });
            }, this);
          }
        }.bind(this));
        binding.selector = selector;
        this._keyBinding[key] = this._keyBinding[key] || [];
        this._keyBinding[key].push(binding);
      }, this);
      this._isBound = true;
    }
  };

  this.unbind = function () {
    if (this.binding && this._isBound) {
      this._keyBinding = {};
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var nodes, events = binding.events || ['change'];
        if (selector[0] === '@') {
          nodes = this.ui[selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(selector));
        }
        nodes.forEach(function (node) {
          if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            utils.forIn(events, function (eventName) {
              this.stopListening(node, eventName + ':bound');
            }, this);
          }
        }.bind(this));
      }, this);
    }
  };

  this.setDOMValue = function (key, value) {
    if (utils.has(this._keyBinding, key)) {
      utils.forIn(this._keyBinding[key], function (binding) {
        var nodes, domValue = utils.clone(value);
        if (binding.selector[0] === '@') {
          nodes = this.ui[binding.selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (binding.selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(binding.selector));
        }
        if (binding.format) {
          if (utils.isString(binding.format)) {
            binding.format = this[binding.format];
          }
          domValue = binding.format.call(this, value);
        }
        nodes.forEach(function (node) {
          var currentValue, strValue = ('undefined' === typeof domValue  || domValue === null) ? '' : domValue.toString();
          if (binding.property) {
            utils.setObjectPath(node, binding.property, domValue);
          } else if (binding.attribute) {
            node.setAttribute(binding.attribute, strValue);
          } else if (binding.html) {
            node.innerHTML = strValue;
          } else if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            if (node.type.toLowerCase() === 'checkbox') {
              currentValue = node.checked;
              node.checked = (domValue === true || domValue === 1 || domValue === 'true' || domValue === 'on');
              if (currentValue !== node.checked) {
                node.dispatchEvent(utils.createEvent('change'));
              }
            } else if (node.type.toLowerCase() === 'radio') {
              currentValue = node.checked;
              node.checked = node.value === strValue;
              if (currentValue !== node.checked) {
                node.dispatchEvent(utils.createEvent('change'));
              }
            } else {
              currentValue = node.value;
              node.value = strValue;
              if (currentValue !== node.value) {
                node.dispatchEvent(utils.createEvent('change'));
              }
            }
          } else {
            node.textContent = strValue;
          }
        });
      }, this);
    }
  };

  this.setDataValue = function (key, value) {
    var e, oldValue = utils.getObjectPath(this.data, key);
    if (oldValue !== value) {
      utils.setObjectPath(this.data, key, value);
      e = utils.createEvent('datachange', { key: key, oldValue: oldValue, newValue: value });
      this.el.dispatchEvent(e);
    }
  };

  if (utils.has(this, 'render')) {
    this.around('render', function (render, data) {
      this.unbind();
      render.call(this, data);
      this.bind();
      this.update(data);
    });
  }

  this.before('initialize', function (options) {
    this.data = this.data || options.data || {};
    this.bind();
    this.update();
  });
};
<commit_msg>adds better error handling for missing dom elements<commit_after>var utils = require('../utils');

module.exports = function Bound () {

  function traverse (obj, key, cb, thisArg) {
    var value;
    for (var name in obj) {
      value = obj[name];
      var newKey = key ? key + '.' + name : name;
      if ('object' === typeof value) {
        traverse(value, newKey, cb, thisArg);
      } else {
        cb.call(thisArg, newKey, value);
      }
    }
  }

  this.defaults = utils.defaults({}, this.defaults, {
    data: null,
    binding: null
  });

  this.update = function (obj) {
    if (obj) {
      var oldData = this.data;
      this.data = obj;
      var e = utils.createEvent('datachange', { oldValue: oldData, newValue: this.data });
      this.el.dispatchEvent(e);
    }
    traverse(this.data, '', this.setDOMValue, this);
  };

  this.readInput = function () {
    if (this.binding) {
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var nodes, key = binding.key;
        if (selector[0] === '@') {
          nodes = this.ui[selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(selector));
        }
        nodes.forEach(function (node) {
          if (!node) {
            throw new Error(this + ' DOM Element not found for ' + selector);
          }
          if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            var value;
            if (node.type.toLowerCase() === 'checkbox') {
              value = node.checked;
            } else if (node.type.toLowerCase() === 'radio') {
              value = 'undefined' === typeof node.value ? 1 : node.value;
              value = node.checked ? value : undefined;
            } else {
              value = node.value;
            }
            this.set(key, value);
          }
        }.bind(this));
      }, this);
    }
  };

  this.reset = function () {
    if (this.binding) {
      this._keyBinding = this._keyBinding || {};
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var key = binding.key;
        this.setDOMValue(key, null);
      }, this);
    }
  };

  this.set = function (key, value) {
    if (!utils.isString(key)) {
      traverse(key, '', this.set, this);
    } else {
      this.setDataValue(key, value);
      this.setDOMValue(key, value);
    }
  };

  this.get = function (key) {
    return utils.getObjectPath(this.data, key);
  };

  this.bind = function () {
    if (this.binding) {
      this._keyBinding = this._keyBinding || {};
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var nodes, key = binding.key, events = binding.events || ['change'];
        if (selector[0] === '@') {
          nodes = this.ui[selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(selector));
        }
        nodes.forEach(function (node) {
          if (!node) {
            throw new Error(this + ' DOM Element not found for ' + selector);
          }
          if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            utils.forIn(events, function (eventName) {
              this.listenTo(node, eventName + ':bound', function (event) {
                var value, el = event.target;
                if (el.type.toLowerCase() === 'checkbox') {
                  value = el.checked;
                } else if (el.type.toLowerCase() === 'radio') {
                  value = 'undefined' === typeof el.value ? 1 : el.value;
                  value = el.checked ? value : undefined;
                } else {
                  value = el.value;
                }
                this.set(key, value);
              });
            }, this);
          }
        }.bind(this));
        binding.selector = selector;
        this._keyBinding[key] = this._keyBinding[key] || [];
        this._keyBinding[key].push(binding);
      }, this);
      this._isBound = true;
    }
  };

  this.unbind = function () {
    if (this.binding && this._isBound) {
      this._keyBinding = {};
      utils.forIn(this.binding, function (binding, selector) {
        if (utils.isString(binding)) {
          binding = { key: binding };
        }
        var nodes, events = binding.events || ['change'];
        if (selector[0] === '@') {
          nodes = this.ui[selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(selector));
        }
        nodes.forEach(function (node) {
          if (!node) {
            throw new Error(this + ' DOM Element not found for ' + selector);
          }
          if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            utils.forIn(events, function (eventName) {
              this.stopListening(node, eventName + ':bound');
            }, this);
          }
        }.bind(this));
      }, this);
    }
  };

  this.setDOMValue = function (key, value) {
    if (utils.has(this._keyBinding, key)) {
      utils.forIn(this._keyBinding[key], function (binding) {
        var nodes, domValue = utils.clone(value);
        if (binding.selector[0] === '@') {
          nodes = this.ui[binding.selector.substr(1)];
          if (!Array.isArray(nodes) && !(nodes instanceof NodeList)) {
            nodes = [nodes];
          }
        } else if (binding.selector === '$') {
          nodes = [this.el];
        } else {
          nodes = [].slice.call(this.selectAll(binding.selector));
        }
        if (binding.format) {
          if (utils.isString(binding.format)) {
            binding.format = this[binding.format];
          }
          domValue = binding.format.call(this, value);
        }
        nodes.forEach(function (node) {
          if (!node) {
            throw new Error(this + ' DOM Element not found for ' + binding.selector);
          }
          var currentValue, strValue = ('undefined' === typeof domValue  || domValue === null) ? '' : domValue.toString();
          if (binding.property) {
            utils.setObjectPath(node, binding.property, domValue);
          } else if (binding.attribute) {
            node.setAttribute(binding.attribute, strValue);
          } else if (binding.html) {
            node.innerHTML = strValue;
          } else if (node.tagName === 'INPUT' || node.tagName === 'SELECT' || node.tagName === 'TEXTAREA') {
            if (node.type.toLowerCase() === 'checkbox') {
              currentValue = node.checked;
              node.checked = (domValue === true || domValue === 1 || domValue === 'true' || domValue === 'on');
              if (currentValue !== node.checked) {
                node.dispatchEvent(utils.createEvent('change'));
              }
            } else if (node.type.toLowerCase() === 'radio') {
              currentValue = node.checked;
              node.checked = node.value === strValue;
              if (currentValue !== node.checked) {
                node.dispatchEvent(utils.createEvent('change'));
              }
            } else {
              currentValue = node.value;
              node.value = strValue;
              if (currentValue !== node.value) {
                node.dispatchEvent(utils.createEvent('change'));
              }
            }
          } else {
            node.textContent = strValue;
          }
        });
      }, this);
    }
  };

  this.setDataValue = function (key, value) {
    var e, oldValue = utils.getObjectPath(this.data, key);
    if (oldValue !== value) {
      utils.setObjectPath(this.data, key, value);
      e = utils.createEvent('datachange', { key: key, oldValue: oldValue, newValue: value });
      this.el.dispatchEvent(e);
    }
  };

  if (utils.has(this, 'render')) {
    this.around('render', function (render, data) {
      this.unbind();
      render.call(this, data);
      this.bind();
      this.update(data);
    });
  }

  this.before('initialize', function (options) {
    this.data = this.data || options.data || {};
    this.bind();
    this.update();
  });
};
<|endoftext|>


<commit_before>'use strict';
var Block;
var bcoin = require('bcoin');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var async = require('async');
var _ = require('underscore');

var config = require('../config');
var Transaction = mongoose.model('Transaction');
var workerService = require('../services/worker');

var BlockSchema = new Schema({
  network: String,
  mainChain: Boolean,
  height: Number,
  hash: String,
  version: Number,
  merkleRoot: String,
  time: Date,
  timeNormalized: Date,
  nonce: Number,
  previousBlockHash: String,
  nextBlockHash: String,
  transactions: [String],
  transactionCount: Number,
  size: Number,
  bits: Number,
  reward: Number,
  processed: Boolean
});

BlockSchema.index({hash: 1}, {unique: true});
BlockSchema.index({height: 1});
BlockSchema.index({time: 1});
BlockSchema.index({timeNormalized: 1});
BlockSchema.index({mainChain: 1});
BlockSchema.index({previousBlockHash: 1, mainChain: 1});

BlockSchema.statics.addBlock = function(block, callback){
  var blockTime = block.ts * 1000;
  var blockTimeNormalized;
  var height;
  async.series([
    function(cb){
      Block.handleReorg(block, cb);
    },
    function(cb){
      Block.findOne({hash: bcoin.util.revHex(block.prevBlock)}, function(err, previousBlock) {
        if(err) {
          return cb(err);
        }
        if(!previousBlock &&
          block.prevBlock !== bcoin.network.get(config.network).genesis.hash &&
          block.prevBlock !== '0000000000000000000000000000000000000000000000000000000000000000'){
          return cb(new Error('No previous block found'));
        }
        blockTimeNormalized = blockTime;
        if(previousBlock && blockTime <= previousBlock.timeNormalized.getTime()) {
          blockTimeNormalized = previousBlock.timeNormalized.getTime() + 1;
        }
        height = (previousBlock && previousBlock.height + 1) || 1;
        Block.update({hash: block.rhash()}, {
          network: config.network,
          mainChain: true,
          height: height,
          version: block.version,
          previousBlockHash: bcoin.util.revHex(block.prevBlock),
          merkleRoot: block.merkleRoot,
          time: new Date(blockTime),
          timeNormalized: new Date(blockTimeNormalized),
          bits: block.bits,
          nonce: block.nonce,
          transactions: block.txs.map(function(tx){return tx.rhash();}),
          transactionCount: block.txs.length,
          size: block._size,
          reward: bcoin.consensus.getReward(height, bcoin.network.get(config.network).halvingInterval)
        }, {upsert: true}, function(err){
          if (err){
            return cb(err);
          }
          if (!previousBlock){
            return cb();
          }
          previousBlock.nextBlockHash = block.rhash();
          previousBlock.save(cb);
        });
      });
    },
    function(cb){
      async.eachLimit(block.txs, workerService.workerCount(), function(transaction, txCb) {
        workerService.sendTask('syncTransactionAndOutputs', {
          transaction: transaction.frame().data.toString('hex'),
          blockHeight: height,
          blockHash: block.rhash(),
          blockTime: blockTime,
          blockTimeNormalized: blockTimeNormalized,
          mainChain: true,
          mempool: false
        }, txCb);
      }, cb);
    },
    function(cb) {
      async.eachLimit(block.txs, workerService.workerCount(), function(transaction, txCb) {
        workerService.sendTask('syncTransactionInputs', transaction.rhash(), txCb);
      }, cb);
    } // TODO need to add a step here to sweep the mempool and mark/sweep anything no longer valid
  ], function(err){
    if(err){
      return callback(err);
    }
    Block.update({hash: block.rhash()}, {$set: {processed:true}}, callback);
  });
};

BlockSchema.statics.getPoolInfo = function(coinbase){
  //TODO need to make this actually parse the coinbase input and map to miner strings
  // also should go somewhere else
  return 'miningPool';
};

BlockSchema.statics.getLocalTip = function(callback) {
  Block.find({mainChain: true, processed: true, network: config.network}).sort({height: -1}).limit(1).exec(function(err, bestBlock) {
    if(err) {
      return callback(err);
    }
    bestBlock = bestBlock[0] || {height: 0};
    callback(null, bestBlock);
  });
};

BlockSchema.statics.getLocatorHashes = function(callback) {
  Block.find({mainChain: true, processed: true, network: config.network}).sort({height: -1}).limit(30).exec(function(err, locatorBlocks) {
    if(err) {
      return callback(err);
    }
    if(locatorBlocks.length < 2) {
      return callback(null, [Array(65).join('0')]);
    }
    locatorBlocks = _.pluck(locatorBlocks, 'hash');
    callback(null, locatorBlocks);
  });
};

BlockSchema.statics.handleReorg = function(block, callback) {
  Block.getLocalTip(function(err, localTip) {
    if(err) {
      return callback(err);
    }
    if(localTip.hash === bcoin.util.revHex(block.prevBlock)) {
      return callback();
    }
    if(localTip.height === 0){
      return callback();
    }
    console.log('Reorg detected at height ' + localTip.height);
    console.log('Local tip was ' + localTip.hash);
    console.log('Incoming block had previous block' + bcoin.util.revHex(block.prevBlock));
    Block.update({mainChain: true, network: config.network, height: {$gte: localTip.height}},
      {$set: {mainChain: false}}, {multi: true}, function(err) {
        if(err) {
          return callback(err);
        }
        Transaction.update({network: config.network, height: {$gte: localTip.height}},
          {$set: {mainChain: false}}, {multi: true}, function(err) {
            if(err) {
              return callback(err);
            }
            callback(new Error('REORG'));
          });
      });
  });
};

BlockSchema.statics._apiTransform = function(block, options){
  var transform = {
    hash: block.hash,
    height: block.height,
    version: block.version,
    size: block.size,
    merkleRoot: block.merkleRoot,
    time: block.time.getTime()/1000,
    timeNormalized: block.timeNormalized,
    nonce: block.nonce,
    bits: block.bits,
    difficulty: block.difficulty,
    chainWork: block.chainWork,
    previousBlockHash: block.previousBlockHash,
    nextBlockHash: block.nextBlockHash,
    reward: block.reward,
    isMainChain: block.mainChain,
    minedBy: Block.getPoolInfo(block.minedBy)
  };
  if(options && options.object){
    return transform;
  }
  return JSON.stringify(transform);
};

module.exports = Block = mongoose.model('Block', BlockSchema);<commit_msg>removing tx list. redundant<commit_after>'use strict';
var Block;
var bcoin = require('bcoin');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var async = require('async');
var _ = require('underscore');

var config = require('../config');
var Transaction = mongoose.model('Transaction');
var workerService = require('../services/worker');

var BlockSchema = new Schema({
  network: String,
  mainChain: Boolean,
  height: Number,
  hash: String,
  version: Number,
  merkleRoot: String,
  time: Date,
  timeNormalized: Date,
  nonce: Number,
  previousBlockHash: String,
  nextBlockHash: String,
  transactionCount: Number,
  size: Number,
  bits: Number,
  reward: Number,
  processed: Boolean
});

BlockSchema.index({hash: 1}, {unique: true});
BlockSchema.index({height: 1});
BlockSchema.index({time: 1});
BlockSchema.index({timeNormalized: 1});
BlockSchema.index({mainChain: 1});
BlockSchema.index({previousBlockHash: 1, mainChain: 1});

BlockSchema.statics.addBlock = function(block, callback){
  var blockTime = block.ts * 1000;
  var blockTimeNormalized;
  var height;
  async.series([
    function(cb){
      Block.handleReorg(block, cb);
    },
    function(cb){
      Block.findOne({hash: bcoin.util.revHex(block.prevBlock)}, function(err, previousBlock) {
        if(err) {
          return cb(err);
        }
        if(!previousBlock &&
          block.prevBlock !== bcoin.network.get(config.network).genesis.hash &&
          block.prevBlock !== '0000000000000000000000000000000000000000000000000000000000000000'){
          return cb(new Error('No previous block found'));
        }
        blockTimeNormalized = blockTime;
        if(previousBlock && blockTime <= previousBlock.timeNormalized.getTime()) {
          blockTimeNormalized = previousBlock.timeNormalized.getTime() + 1;
        }
        height = (previousBlock && previousBlock.height + 1) || 1;
        Block.update({hash: block.rhash()}, {
          network: config.network,
          mainChain: true,
          height: height,
          version: block.version,
          previousBlockHash: bcoin.util.revHex(block.prevBlock),
          merkleRoot: block.merkleRoot,
          time: new Date(blockTime),
          timeNormalized: new Date(blockTimeNormalized),
          bits: block.bits,
          nonce: block.nonce,
          transactionCount: block.txs.length,
          size: block._size,
          reward: bcoin.consensus.getReward(height, bcoin.network.get(config.network).halvingInterval)
        }, {upsert: true}, function(err){
          if (err){
            return cb(err);
          }
          if (!previousBlock){
            return cb();
          }
          previousBlock.nextBlockHash = block.rhash();
          previousBlock.save(cb);
        });
      });
    },
    function(cb){
      async.eachLimit(block.txs, workerService.workerCount(), function(transaction, txCb) {
        workerService.sendTask('syncTransactionAndOutputs', {
          transaction: transaction.frame().data.toString('hex'),
          blockHeight: height,
          blockHash: block.rhash(),
          blockTime: blockTime,
          blockTimeNormalized: blockTimeNormalized,
          mainChain: true,
          mempool: false
        }, txCb);
      }, cb);
    },
    function(cb) {
      async.eachLimit(block.txs, workerService.workerCount(), function(transaction, txCb) {
        workerService.sendTask('syncTransactionInputs', transaction.rhash(), txCb);
      }, cb);
    } // TODO need to add a step here to sweep the mempool and mark/sweep anything no longer valid
  ], function(err){
    if(err){
      return callback(err);
    }
    Block.update({hash: block.rhash()}, {$set: {processed:true}}, callback);
  });
};

BlockSchema.statics.getPoolInfo = function(coinbase){
  //TODO need to make this actually parse the coinbase input and map to miner strings
  // also should go somewhere else
  return 'miningPool';
};

BlockSchema.statics.getLocalTip = function(callback) {
  Block.find({mainChain: true, processed: true, network: config.network}).sort({height: -1}).limit(1).exec(function(err, bestBlock) {
    if(err) {
      return callback(err);
    }
    bestBlock = bestBlock[0] || {height: 0};
    callback(null, bestBlock);
  });
};

BlockSchema.statics.getLocatorHashes = function(callback) {
  Block.find({mainChain: true, processed: true, network: config.network}).sort({height: -1}).limit(30).exec(function(err, locatorBlocks) {
    if(err) {
      return callback(err);
    }
    if(locatorBlocks.length < 2) {
      return callback(null, [Array(65).join('0')]);
    }
    locatorBlocks = _.pluck(locatorBlocks, 'hash');
    callback(null, locatorBlocks);
  });
};

BlockSchema.statics.handleReorg = function(block, callback) {
  Block.getLocalTip(function(err, localTip) {
    if(err) {
      return callback(err);
    }
    if(localTip.hash === bcoin.util.revHex(block.prevBlock)) {
      return callback();
    }
    if(localTip.height === 0){
      return callback();
    }
    console.log('Reorg detected at height ' + localTip.height);
    console.log('Local tip was ' + localTip.hash);
    console.log('Incoming block had previous block' + bcoin.util.revHex(block.prevBlock));
    Block.update({mainChain: true, network: config.network, height: {$gte: localTip.height}},
      {$set: {mainChain: false}}, {multi: true}, function(err) {
        if(err) {
          return callback(err);
        }
        Transaction.update({network: config.network, height: {$gte: localTip.height}},
          {$set: {mainChain: false}}, {multi: true}, function(err) {
            if(err) {
              return callback(err);
            }
            callback(new Error('REORG'));
          });
      });
  });
};

BlockSchema.statics._apiTransform = function(block, options){
  var transform = {
    hash: block.hash,
    height: block.height,
    version: block.version,
    size: block.size,
    merkleRoot: block.merkleRoot,
    time: block.time.getTime()/1000,
    timeNormalized: block.timeNormalized,
    nonce: block.nonce,
    bits: block.bits,
    difficulty: block.difficulty,
    chainWork: block.chainWork,
    previousBlockHash: block.previousBlockHash,
    nextBlockHash: block.nextBlockHash,
    reward: block.reward,
    isMainChain: block.mainChain,
    minedBy: Block.getPoolInfo(block.minedBy)
  };
  if(options && options.object){
    return transform;
  }
  return JSON.stringify(transform);
};

module.exports = Block = mongoose.model('Block', BlockSchema);<|endoftext|>


<commit_before>'use strict';

import gulp from 'gulp';
import path from 'path';
import _    from 'lodash';
import {paths, plugins, config} from '../config';

const inject = (_path, _ext, _options = {}) => {
  const ext  = config.isDevelop ? _ext : `min.${_ext}`;
  const file = `${_path}.${ext}`;
  const name = _path.match(/\w+$/)[0];
  const base = paths.dest.folders.base;

  const source = gulp.src(path.join(base, file), {read: false});
  const options = _.merge({ignorePath: base, name}, _options);

  return [source, options];
}

gulp.task('build:index',  () => {
  return gulp.src(paths.source.files.indexHTML)
    .pipe( plugins.pug( config.isDevelop ? {pretty: true} : null ) )
    .pipe( plugins.inject(...inject('js/vendor', 'js')) )
    .pipe( plugins.inject(...inject('css/vendor', 'css')) )
    .pipe( plugins.inject(...inject('js/build', 'js')) )
    .pipe( plugins.inject(...inject('css/build', 'css')) )
    .pipe( gulp.dest(paths.dest.folders.base) );
});<commit_msg>build:index - добавил jsdoc к функции inject<commit_after>'use strict';

import gulp from 'gulp';
import path from 'path';
import _    from 'lodash';
import {paths, plugins, config} from '../config';

/** @description - Функция для преобразования аргументов для плагина gulp-inject
 *
 * @param {string} _path - Путь до файла без расширения
 * @param {string} _ext - Расширение файла
 * @param {object} _options - Дополнительные опции для плагина gulp-inject
 * @return {array}  - Возвращает массив из элементов [Путь к файлу с расширением, Опции]
 */
const inject = (_path, _ext, _options = {}) => {
  const ext  = config.isDevelop ? _ext : `min.${_ext}`;
  const file = `${_path}.${ext}`;
  const name = _path.match(/\w+$/)[0];
  const base = paths.dest.folders.base;

  const source = gulp.src(path.join(base, file), {read: false});
  const options = _.merge({ignorePath: base, name}, _options);

  return [source, options];
}

gulp.task('build:index',  () => {
  return gulp.src(paths.source.files.indexHTML)
    .pipe( plugins.pug( config.isDevelop ? {pretty: true} : null ) )
    .pipe( plugins.inject(...inject('js/vendor', 'js')) )
    .pipe( plugins.inject(...inject('css/vendor', 'css')) )
    .pipe( plugins.inject(...inject('js/build', 'js')) )
    .pipe( plugins.inject(...inject('css/build', 'css')) )
    .pipe( gulp.dest(paths.dest.folders.base) );
});<|endoftext|>


<commit_before>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

require('node-path')(module);

/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var validators = require('mongoose-validators');
var log = require('debug')('democracyos:models:topic');
var xss = require('lib/richtext').xssFilter({ video: true, image: true, link: true });

var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

/**
 * Topic Vote Schema
 */

var Vote = new Schema({
    author: { type: ObjectId, ref: 'User', required: true }
  , value: { type: String, enum: ['positive', 'negative', 'neutral'], required: true }
  , trustee: { type: ObjectId, ref: 'User' }
  , caster: { type: ObjectId, ref: 'User' }
  , createdAt: { type: Date, default: Date.now }
  , democracy: { type: ObjectId, ref: 'Deployment' }
});

/**
 * Clause Schema
 */

var ClauseSchema = new Schema({
    clauseName: { type: String }
  , order: { type: Number, required: true, default: 0 }
  , text: { type: String, required: true, default: 'Undefined text' }
  , centered: { type: Boolean, required: true, default: false }
});

ClauseSchema.methods.update = function update(data) {
  data = data || {};
  this.clauseName = data.clauseName || this.clauseName;
  this.order = data.order || this.order;
  this.text = data.text || this.text;
};

mongoose.model('Clause', ClauseSchema);

/**
 * Link
 */

var LinkSchema = new Schema({
    text: { type: String }
  , url: { type: String, validate: validators.isURL({ skipEmpty: true }) }
});

mongoose.model('Link', LinkSchema);

LinkSchema.methods.update = function update(data) {
  data = data || {};
  this.text = data.text || this.text;
  this.url = data.url || this.url;
};

/**
 * Topic Schema
 */

var TopicSchema = new Schema({
    topicId: { type: String }
  , tag: { type: ObjectId, ref: 'Tag', required: true }
  , officialTitle: { type: String, required: false }
  , mediaTitle: { type: String, required: false }
  , author: { type: String }
  , authorUrl: { type: String, validate: validators.isURL({ skipEmpty: true }) }
  , source: { type: String, validate: validators.isURL({ skipEmpty: true }) }
  , summary: { type: String, required: false }
  , clauses: [ClauseSchema]
  , votes: [Vote]
  , participants: [{type: ObjectId, ref: 'User' }]
  , createdAt: { type: Date, default: Date.now }
  , updatedAt: { type: Date }
  , closingAt: { type: Date }
  , publishedAt: { type: Date }
  , deletedAt: { type: Date }
  , votable: { type: Boolean, required: true, default: true }
  , clauseTruncationText: { type: String }
  , links: [LinkSchema]
  , forum: { type: ObjectId, ref: 'Forum', required: false }
});

/**
 * Define Schema Indexes for MongoDB
 */

TopicSchema.index({ createdAt: -1 });
TopicSchema.index({ closingAt: -1 });
TopicSchema.index({ participants: -1 });
TopicSchema.index({ tag: -1 });
TopicSchema.index({ topicId: -1 });

/**
 * Make Schema `.toObject()` and
 * `.toJSON()` parse getters for
 * proper JSON API response
 */

TopicSchema.set('toObject', { getters: true });
TopicSchema.set('toJSON', { getters: true });

TopicSchema.options.toObject.transform =
TopicSchema.options.toJSON.transform = function(doc, ret) {
  if (ret.votes) delete ret.votes;
  if (ret.upvotes) ret.upvotes = ret.upvotes.map(function(v) { return v.author });
  if (ret.downvotes) ret.downvotes = ret.downvotes.map(function(v) { return v.author });
  if (ret.abstentions) ret.abstentions = ret.abstentions.map(function(v) { return v.author });
};

/**
 * -- Model's event hooks
 */

/**
 * Pre update modified time
 *
 * @api private
 */

TopicSchema.pre('save', function(next) {
  this.updatedAt = this.isNew ? this.createdAt : Date.now();
  this.summary = xss(this.summary);

  next();
});

/**
 * -- Model's API extension
 */

/**
 * Compile topicId to generate
 * a human readable title
 *
 * @return {String} clauses
 * @api public
 */

TopicSchema.virtual('title').get(function() {
  return this.topicId;
});

/**
 * Compile clauses to render
 * text content
 *
 * @return {String} clauses
 * @api public
 */

TopicSchema.virtual('content').get(function() {
  if (!this.clauses) return;
  return this.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  }).map(function(c) {
    if (c.text) return (c.clauseName ? c.clauseName + ': ' : '') + c.text;
  }).join('\n');
});

/**
 * Get `positive` votes
 *
 * @return {Array} voters
 * @api public
 */

TopicSchema.virtual('upvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'positive' === v.value;
  });
});

/**
 * Get `negative` votes
 *
 * @return {Array} voters
 * @api public
 */

TopicSchema.virtual('downvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'negative' === v.value;
  });
});

/**
 * Get `neutral` votes
 *
 * @return {Array} voters
 * @api public
 */

TopicSchema.virtual('abstentions').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'neutral' === v.value;
  });
});

/**
 * Get topic `status`
 *
 * @return {String} status
 * @api public
 */

TopicSchema.virtual('status').get(function() {
  if (!this.closingAt) return 'open';

  return this.closingAt.getTime() < Date.now()
    ? 'closed'
    : 'open';
});

/**
 * Wether the `topic` is open
 *
 * @return {Boolean} open
 * @api public
 */

TopicSchema.virtual('open').get(function() {
  return 'open' === this.status;
});

/**
 * Wether the `topic` is closed
 *
 * @return {Boolean} closed
 * @api public
 */

TopicSchema.virtual('closed').get(function() {
  return 'closed' === this.status;
});

/**
 * Wether the `topic` was deleted
 *
 * @return {Boolean} deleted
 * @api public
 */

TopicSchema.virtual('deleted').get(function() {
  return !!this.deletedAt;
});

/**
 * Wether the `topic` is public
 *
 * @return {Boolean} public
 * @api public
 */

TopicSchema.virtual('public').get(function() {
  return !!this.publishedAt;
});

/**
 * Wether the `topic` is draft
 *
 * @return {Boolean} draft
 * @api public
 */

TopicSchema.virtual('draft').get(function() {
  return !this.publishedAt;
});

/**
 * Vote Topic with provided user
 * and voting value
 *
 * @param {User|ObjectId|String} user
 * @param {String} value
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.vote = function(user, value, cb) {
  if ('recount' === this.status) return cb(new Error('Voting is closed on recount.'));
  if ('closed' === this.status) return cb(new Error('Voting is closed.'));
  // Here we could provide a 5000ms tolerance (5s)
  // or something... to prevent false positives
  if (this.closingAt && (+new Date(this.closingAt) < +new Date) ) return cb(new Error('Can\'t vote after closing date.'));

  var vote = { author: user, value: value, caster: user };

  this.unvote(user, onunvote.bind(this));

  function onunvote(err) {
    if (err) {
      if ('function' === typeof cb) return cb(err);
      else throw err;
    }

    this.votes.push(vote);

    // Add user as participant
    this.addParticipant(user);
    if ('function' === typeof cb) this.save(cb);
  }
};

/**
 * Add participant to topic
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.addParticipant = function(user, cb) {
  this.participants.addToSet(user);
  if (cb) this.save(cb);
}

/**
 * Unvote Topic from provided user
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.unvote = function(user, cb) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals ? a.equals(c) : a === c;
  });

  log('About to remove votes %j', voted);
  voted.length && voted.forEach(function(v) {
    var removed = votes.id(v.id).remove();
    log('Remove vote %j', removed);
  });

  if ('function' === typeof cb) this.save(cb);
};

/**
 * Check for vote status of user
 *
 * @param {User|ObjectId|String} user
 * @api public
 */

TopicSchema.methods.votedBy = function(user) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals ? a.equals(c) : a === c;
  });

  return 1 === voted.length;
};

/**
 * Close topic to prevent future vote casts
 *
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.close = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to close topic before closing date.');
    return cb(new Error('Deny to close topic before closing date.'));
  }
  this.status = 'closed';
  cb && this.save(cb);
};

module.exports = function initialize(conn) {
  return conn.model('Topic', TopicSchema);
};
<commit_msg>fix eslint warnings<commit_after>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

require('node-path')(module);

/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var validators = require('mongoose-validators');
var log = require('debug')('democracyos:models:topic');
var xss = require('lib/richtext').xssFilter({ video: true, image: true, link: true });

var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

/**
 * Topic Vote Schema
 */

var Vote = new Schema({
    author: { type: ObjectId, ref: 'User', required: true }
  , value: { type: String, enum: ['positive', 'negative', 'neutral'], required: true }
  , trustee: { type: ObjectId, ref: 'User' }
  , caster: { type: ObjectId, ref: 'User' }
  , createdAt: { type: Date, default: Date.now }
  , democracy: { type: ObjectId, ref: 'Deployment' }
});

/**
 * Clause Schema
 */

var ClauseSchema = new Schema({
    clauseName: { type: String }
  , order: { type: Number, required: true, default: 0 }
  , text: { type: String, required: true, default: 'Undefined text' }
  , centered: { type: Boolean, required: true, default: false }
});

ClauseSchema.methods.update = function update(data) {
  data = data || {};
  this.clauseName = data.clauseName || this.clauseName;
  this.order = data.order || this.order;
  this.text = data.text || this.text;
};

mongoose.model('Clause', ClauseSchema);

/**
 * Link
 */

var LinkSchema = new Schema({
    text: { type: String }
  , url: { type: String, validate: validators.isURL({ skipEmpty: true }) }
});

mongoose.model('Link', LinkSchema);

LinkSchema.methods.update = function update(data) {
  data = data || {};
  this.text = data.text || this.text;
  this.url = data.url || this.url;
};

/**
 * Topic Schema
 */

var TopicSchema = new Schema({
    topicId: { type: String }
  , tag: { type: ObjectId, ref: 'Tag', required: true }
  , officialTitle: { type: String, required: false }
  , mediaTitle: { type: String, required: false }
  , author: { type: String }
  , authorUrl: { type: String, validate: validators.isURL({ skipEmpty: true }) }
  , source: { type: String, validate: validators.isURL({ skipEmpty: true }) }
  , summary: { type: String, required: false }
  , clauses: [ClauseSchema]
  , votes: [Vote]
  , participants: [{type: ObjectId, ref: 'User' }]
  , createdAt: { type: Date, default: Date.now }
  , updatedAt: { type: Date }
  , closingAt: { type: Date }
  , publishedAt: { type: Date }
  , deletedAt: { type: Date }
  , votable: { type: Boolean, required: true, default: true }
  , clauseTruncationText: { type: String }
  , links: [LinkSchema]
  , forum: { type: ObjectId, ref: 'Forum', required: false }
});

/**
 * Define Schema Indexes for MongoDB
 */

TopicSchema.index({ createdAt: -1 });
TopicSchema.index({ closingAt: -1 });
TopicSchema.index({ participants: -1 });
TopicSchema.index({ tag: -1 });
TopicSchema.index({ topicId: -1 });

/**
 * Make Schema `.toObject()` and
 * `.toJSON()` parse getters for
 * proper JSON API response
 */

TopicSchema.set('toObject', { getters: true });
TopicSchema.set('toJSON', { getters: true });

TopicSchema.options.toObject.transform =
TopicSchema.options.toJSON.transform = function(doc, ret) {
  if (ret.votes) delete ret.votes;
  if (ret.upvotes) ret.upvotes = ret.upvotes.map(function(v) { return v.author; });
  if (ret.downvotes) ret.downvotes = ret.downvotes.map(function(v) { return v.author; });
  if (ret.abstentions) ret.abstentions = ret.abstentions.map(function(v) { return v.author; });
};

/**
 * -- Model's event hooks
 */

/**
 * Pre update modified time
 *
 * @api private
 */

TopicSchema.pre('save', function(next) {
  this.updatedAt = this.isNew ? this.createdAt : Date.now();
  this.summary = xss(this.summary);

  next();
});

/**
 * -- Model's API extension
 */

/**
 * Compile topicId to generate
 * a human readable title
 *
 * @return {String} clauses
 * @api public
 */

TopicSchema.virtual('title').get(function() {
  return this.topicId;
});

/**
 * Compile clauses to render
 * text content
 *
 * @return {String} clauses
 * @api public
 */

TopicSchema.virtual('content').get(function() {
  if (!this.clauses) return;
  return this.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  }).map(function(c) {
    if (c.text) return (c.clauseName ? c.clauseName + ': ' : '') + c.text;
  }).join('\n');
});

/**
 * Get `positive` votes
 *
 * @return {Array} voters
 * @api public
 */

TopicSchema.virtual('upvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'positive' === v.value;
  });
});

/**
 * Get `negative` votes
 *
 * @return {Array} voters
 * @api public
 */

TopicSchema.virtual('downvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'negative' === v.value;
  });
});

/**
 * Get `neutral` votes
 *
 * @return {Array} voters
 * @api public
 */

TopicSchema.virtual('abstentions').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'neutral' === v.value;
  });
});

/**
 * Get topic `status`
 *
 * @return {String} status
 * @api public
 */

TopicSchema.virtual('status').get(function() {
  if (!this.closingAt) return 'open';

  return this.closingAt.getTime() < Date.now()
    ? 'closed'
    : 'open';
});

/**
 * Wether the `topic` is open
 *
 * @return {Boolean} open
 * @api public
 */

TopicSchema.virtual('open').get(function() {
  return 'open' === this.status;
});

/**
 * Wether the `topic` is closed
 *
 * @return {Boolean} closed
 * @api public
 */

TopicSchema.virtual('closed').get(function() {
  return 'closed' === this.status;
});

/**
 * Wether the `topic` was deleted
 *
 * @return {Boolean} deleted
 * @api public
 */

TopicSchema.virtual('deleted').get(function() {
  return !!this.deletedAt;
});

/**
 * Wether the `topic` is public
 *
 * @return {Boolean} public
 * @api public
 */

TopicSchema.virtual('public').get(function() {
  return !!this.publishedAt;
});

/**
 * Wether the `topic` is draft
 *
 * @return {Boolean} draft
 * @api public
 */

TopicSchema.virtual('draft').get(function() {
  return !this.publishedAt;
});

/**
 * Vote Topic with provided user
 * and voting value
 *
 * @param {User|ObjectId|String} user
 * @param {String} value
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.vote = function(user, value, cb) {
  if ('recount' === this.status) return cb(new Error('Voting is closed on recount.'));
  if ('closed' === this.status) return cb(new Error('Voting is closed.'));
  // Here we could provide a 5000ms tolerance (5s)
  // or something... to prevent false positives
  if (this.closingAt && (+new Date(this.closingAt) < +new Date) ) return cb(new Error('Can\'t vote after closing date.'));

  var vote = { author: user, value: value, caster: user };

  this.unvote(user, onunvote.bind(this));

  function onunvote(err) {
    if (err) {
      if ('function' === typeof cb) return cb(err);
      else throw err;
    }

    this.votes.push(vote);

    // Add user as participant
    this.addParticipant(user);
    if ('function' === typeof cb) this.save(cb);
  }
};

/**
 * Add participant to topic
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.addParticipant = function(user, cb) {
  this.participants.addToSet(user);
  if (cb) this.save(cb);
};

/**
 * Unvote Topic from provided user
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.unvote = function(user, cb) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals ? a.equals(c) : a === c;
  });

  log('About to remove votes %j', voted);
  if (voted.length) voted.forEach(function(v) {
    var removed = votes.id(v.id).remove();
    log('Remove vote %j', removed);
  });

  if ('function' === typeof cb) this.save(cb);
};

/**
 * Check for vote status of user
 *
 * @param {User|ObjectId|String} user
 * @api public
 */

TopicSchema.methods.votedBy = function(user) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals ? a.equals(c) : a === c;
  });

  return 1 === voted.length;
};

/**
 * Close topic to prevent future vote casts
 *
 * @param {Function} cb
 * @api public
 */

TopicSchema.methods.close = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to close topic before closing date.');
    return cb(new Error('Deny to close topic before closing date.'));
  }
  this.status = 'closed';
  if (cb) this.save(cb);
};

module.exports = function initialize(conn) {
  return conn.model('Topic', TopicSchema);
};
<|endoftext|>


<commit_before>/**
* Pull in required modules
**/
const Constants     = require('../constants');
const async         = require('async');
const url           = require('url');
const dns           = require('dns');
const _             = require('lodash');
const os            = require('os');
const S             = require('string');
const querystring   = require('querystring');

/**
* Expose our creation class that will be called
* with properties.
**/
module.exports = exports = function(params) {

  /**
  * The Test object to return that we can use
  **/
  var Test = [];

  /**
  * Adds the array of tests if given
  **/
  if(params.rules && Array.isArray(params.rules || []) === true) {

    // concat our list
    Test = Test.concat(params.rules || []);

  } else if(params.rules && typeof params.rules === 'function') {

    // add that
    Test = Test.concat([ params.rules ]);

  }

  /**
  * Get the sections of the key
  **/
  var sections = (params.key || '').split('.');

  /**
  * Defines the options we can use
  **/
  Test.getKey = function() {

    // returns the key
    return params.key;

  };

  /**
  * Returns the defined test key
  **/
  Test.getTest = function() {

    // returns the key
    return params.test || sections[1] || 'unknown';

  };

  /**
  * Returns the defined category key
  **/
  Test.getCategory = function() {

    // returns the key
    return params.category || sections[0] || 'unknown';

  };

  /**
  * Returns the current version of the test
  **/
  Test.getVersion = function() {

    // done
    return params.version || null;

  };

  /**
  * Fired when the test timed out from the parent worker
  **/
  Test.onTimeout = function(logger, fn) {

    // just finish for now
    fn(null);

  };

  /**
  * Runs on boot for the test
  **/
  Test.bootstrap = function(logger, fn) {

    // just finish for now
    fn(null);

  };

  // return the Test object to use
  return Test;

};
<commit_msg>Added a function that can return the current configured rules<commit_after>/**
* Pull in required modules
**/
const Constants     = require('../constants');
const async         = require('async');
const url           = require('url');
const dns           = require('dns');
const _             = require('lodash');
const os            = require('os');
const S             = require('string');
const querystring   = require('querystring');

/**
* Expose our creation class that will be called
* with properties.
**/
module.exports = exports = function(params) {

  /**
  * The Test object to return that we can use
  **/
  var Test = [];

  /**
  * Adds the array of tests if given
  **/
  if(params.rules && Array.isArray(params.rules || []) === true) {

    // concat our list
    Test = Test.concat(params.rules || []);

  } else if(params.rules && typeof params.rules === 'function') {

    // add that
    Test = Test.concat([ params.rules ]);

  }

  /**
  * Get the sections of the key
  **/
  var sections = (params.key || '').split('.');

  /**
  * Returns the rules given in a array that
  * can be called by workers implementing these tests
  **/
  Test.getRules = function() {

    // returns the key
    return params.rules || [];

  };

  /**
  * Defines the options we can use
  **/
  Test.getKey = function() {

    // returns the key
    return params.key;

  };

  /**
  * Returns the defined test key
  **/
  Test.getTest = function() {

    // returns the key
    return params.test || sections[1] || 'unknown';

  };

  /**
  * Returns the defined category key
  **/
  Test.getCategory = function() {

    // returns the key
    return params.category || sections[0] || 'unknown';

  };

  /**
  * Returns the current version of the test
  **/
  Test.getVersion = function() {

    // done
    return params.version || null;

  };

  /**
  * Fired when the test timed out from the parent worker
  **/
  Test.onTimeout = function(logger, fn) {

    // just finish for now
    fn(null);

  };

  /**
  * Runs on boot for the test
  **/
  Test.bootstrap = function(logger, fn) {

    // just finish for now
    fn(null);

  };

  // return the Test object to use
  return Test;

};
<|endoftext|>


<commit_before>var spawn = require('child_process').spawn;
var spawnSync = require('child_process').spawnSync;
var path = require('path');
var chokidarLib = require('chokidar');
var colors = require('colors/safe');
var configPath = '';
var spawnedCommands = [];
var chokidar = null;
var config = {};

RegExp.prototype.toJSON = RegExp.prototype.toString;
Function.prototype.toJSON = function() {
    return this.toString().replace(/\n/g, '');
};

var watcher = function(initialConfig) {
    var noConfigFound = false;

    initialConfig = initialConfig || path.resolve(process.cwd(), 'watcherConfig.js');

    if (initialConfig.constructor === String) {
        var resolvedPath = path.resolve(initialConfig);
        configPath = resolvedPath;

        try {
            config = require(resolvedPath);
        } catch (e) {
            config = {};
            configPath = '';
            noConfigFound = true;
        }
    }

    ensureConfigValues();

    if (noConfigFound) {
        console.log(colors.yellow('Couldn\'t find the config file »' + resolvedPath + '«. Will use default values.'));
        console.log(colors.yellow('Default config values are:'));
        console.log(JSON.stringify(config, null, 2));
        console.log();
    } else {
        if (config.verbosity >= verbosity.normal) {
            console.log(colors.green('Using following config (' + configPath + '):'));
            console.log(JSON.stringify(config, null, 2));
            console.log();
        }
    }

    if (!config.commandsOnChange.length) {
        config.commandsOnChange.push({
            command: ''
        });
    }

    setupCommandsOnStart();
    setupCommandsOnChange();

    process.on('SIGTERM', (function(self) {
        return function() {
            for (var index in spawnedCommands) {
                var command = spawnedCommands[index];

                if (command) {
                    command.kill('SIGINT');
                }
            }

            if (chokidar) {
                chokidar.close();
            }

            for (var index in config.commandsOnEnd) {
                spawnCommands(config.commandsOnEnd[index].command, true);
            }
        };
    })(this));

};

var ensureConfigValues = function() {
    config.directory = config.directory || process.cwd();
    config.delay = config.delay || 1000;
    config.ignore = config.ignore || [];
    config.verbosity = config.verbosity || 'normal';
    config.verbosity = verbosity[config.verbosity];
    config.commandsOnStart = config.commandsOnStart || [];
    config.commandsOnChange = config.commandsOnChange || [];
    config.commandsOnEnd = config.commandsOnEnd || [];
};

var setupCommandsOnStart = function() {
    for (var index in config.commandsOnStart) {
        if (config.commandsOnStart[index].command.constructor === String) {
            var delay = config.commandsOnStart[index].delay || 0;

            setTimeout(function(index) {
                var commands = spawnCommands(config.commandsOnStart[index].command);
                Array.prototype.push.apply(spawnedCommands, commands);
            }, delay, index);
        } else if (config.commandsOnStart[index].command.constructor === Function) {
            var callbackResult = config.commandsOnStart[index].command();

            var delay = config.commandsOnStart[index].delay || 0;

            setTimeout(function(callbackResult) {
                var commands = spawnCommands(callbackResult);
                Array.prototype.push.apply(spawnedCommands, commands);
            }, delay, callbackResult);
        }
    }
};

var setupCommandsOnChange = function() {
    var timeoutID = 0;
    var allEvents = [];

    if (config.commandsOnChange.length) {
        var directoryToWatch = config.directory.indexOf('/') !== 0 ? path.resolve(process.cwd(), config.directory) : config.directory;

        chokidar = chokidarLib.watch(directoryToWatch, {
            ignoreInitial: true,
            ignored: config.ignore
        });

        chokidar.on('all', function(event, path) {
            clearTimeout(timeoutID);
            allEvents.push([event, path]);

            if (configPath && event === 'change' && configPath === path) { // The config file changed
                delete require.cache[configPath];
                config = require(configPath);
                ensureConfigValues();
                if (config.verbosity >= verbosity.normal) {
                    console.log(colors.green('Config file changed, reloaded it. The new values are:'));
                    console.log(JSON.stringify(config, null, 2));
                    console.log();
                }
                allEvents = [];
                return;
            }

            timeoutID = setTimeout(function() {
                if (config.verbosity >= verbosity.verbose) {
                    console.log(colors.green('Executing callbacks for the following events:'));
                    console.log(JSON.stringify(allEvents, null, 2));
                    console.log();
                }

                var commandsWereSpawned = false;

                for (var index in config.commandsOnChange) {
                    if (config.commandsOnChange[index].command.constructor === String) {
                        var delay = config.commandsOnChange[index].delay || 0;

                        setTimeout(function(index) {
                            spawnCommands(config.commandsOnChange[index].command);
                        }, delay, index);

                        commandsWereSpawned = config.commandsOnChange[index].command !== '';
                    } else if (config.commandsOnChange[index].command.constructor === Function) {
                        for (var i = 0; i < allEvents.length; i++) {
                            var event = allEvents[i][0];
                            var file = allEvents[i][1];
                            var callbackResult = config.commandsOnChange[index].command(event, file);

                            var delay = config.commandsOnChange[index].delay || 0;

                            setTimeout(function(callbackResult) {
                                spawnCommands(callbackResult);
                            }, delay, callbackResult);

                            commandsWereSpawned = callbackResult !== '';
                        }
                    }
                }

                if (!commandsWereSpawned) {
                    if (config.verbosity >= verbosity.verbose) {
                        console.log(colors.yellow('No callbacks were called.'));
                        console.log();
                    }
                }

                allEvents = [];
            }, config.delay);
        });
    }
};

var spawnCommands = function(commandString, sync) {
    var parsedCommands = parseCommands(commandString);
    var spawnedCommands = [];

    for (var index in parsedCommands) {
        var parsedCommand = parsedCommands[index];

        if (config.verbosity >= verbosity.normal) {
            console.log(colors.green('Spawning the command') + ' "' + parsedCommand.command + '" ' + colors.green('with the arguments ') + JSON.stringify(parsedCommand.arguments));
            console.log();
        }

        var child;

        if (!sync) {
            child = spawn(parsedCommand.command, parsedCommand.arguments, {
                stdio: 'inherit'
            });

            var onExit = (function(parsedCommand) {
                return function() {
                    if (config.verbosity >= verbosity.normal) {
                        console.log(colors.green('The command') + ' "' + parsedCommand.command + '" ' + colors.green('with arguments ') + JSON.stringify(parsedCommand.arguments) + colors.green(' exited'));
                        console.log();
                    }
                };
            })(parsedCommand);

            child.on('exit', onExit);
            child.on('SIGTERM', onExit);
            child.on('SIGINT', onExit);
        } else {
            spawnSync(parsedCommand.command, parsedCommand.arguments, {
                stdio: 'inherit'
            });
        }

        spawnedCommands.push(child);
    }

    return spawnedCommands;
};

var parseCommands = function(commandString) {
    var commands = [];

    if (commandString) {
        var parsedCommand = parseCommand(commandString);

        commands.push({
            command: parsedCommand.command,
            arguments: parsedCommand.arguments
        });

        Array.prototype.push.apply(commands, parseCommands(parsedCommand.remainingString));
    }

    return commands;
};

var parseCommand = function(command) {
    var quoted = false;
    var parsedCommand = {
        command: '',
        arguments: [],
        remainingString: ''
    };

    if (!command) {
        return parsedCommand;
    }

    var checkForArguments = function() {
        var remainingString = command.replace(parsedCommand.command, '').trim();

        if (remainingString.indexOf(';') === 0) {
            parsedCommand.remainingString = remainingString.substring(1).trim();
            return parsedCommand;
        }

        var parsedArguments = parseArguments(remainingString);

        parsedCommand.arguments = parsedArguments.arguments;
        parsedCommand.remainingString = parsedArguments.remainingString;
        return parsedCommand;
    };

    for (var i = 0; i < command.length; i++) {
        var char = command.charAt(i);
        var previousChar = i > 0 ? command.charAt(i - 1) : '';

        if (char === ' ') {
            if (quoted || previousChar === '\\') {
                parsedCommand.command += char;
            } else {
                return checkForArguments();
            }
        } else if (char === '"' || char === '\'') {
            if (quoted) {
                return checkForArguments();
            }

            quoted = true;
        } else if (char === ';' && !quoted) {
            return checkForArguments();
        } else if (char === '&' && previousChar === '&' && !quoted) {
            return checkForArguments();
        } else {
            parsedCommand.command += char;
        }
    }

    return checkForArguments();
};

var parseArguments = function(arguments) {
    var quoted = false;
    var parentheses = 0;
    var brackets = 0;
    var braces = 0;
    var parsedArguments = {
        arguments: [],
        remainingString: ''
    };

    if (!arguments) {
        return parsedArguments;
    }

    var currentArgument = '';

    var addArgument = function() {
        currentArgument = currentArgument.trim();

        if (currentArgument.indexOf('"') === 0 && currentArgument.match(/"$/)) {
            currentArgument = currentArgument.substring(1, currentArgument.length - 1);
        }

        if (currentArgument !== '' && currentArgument !== '&') {
            parsedArguments.arguments.push(currentArgument);
        }

        currentArgument = '';
        quoted = false;
        var parentheses = 0;
        var brackets = 0;
        var braces = 0;
    };

    for (var i = 0; i < arguments.length; i++) {
        var char = arguments[i];
        var previousChar = i > 0 ? arguments.charAt(i - 1) : '';

        if (char === ' ') {
            if (quoted || previousChar === '\\') {
                currentArgument += char;
            } else {
                addArgument();
            }
        } else if (char === '"' || char === '\'') {
            currentArgument += char;

            if (quoted && !parentheses && !brackets && !braces) {
                addArgument();
            } else if (quoted) {
                quoted = false;
            } else {
                quoted = true;
            }
        } else if (char === '(') {
            currentArgument += char;
            parentheses++;

        } else if (char === ')') {
            currentArgument += char;
            parentheses--;

            if (!parentheses && !brackets && !braces && !quoted) {
                addArgument();
            }
        } else if (char === '[') {
            currentArgument += char;
            brackets++;
        } else if (char === ']') {
            currentArgument += char;
            brackets--;

            if (!parentheses && !brackets && !braces && !quoted) {
                addArgument();
            }
        } else if (char === '{') {
            currentArgument += char;
            braces++;
        } else if (char === '}') {
            currentArgument += char;
            braces--;

            if (!parentheses && !brackets && !braces && !quoted) {
                addArgument();
            }
        } else if (char === ';') {
            if (!quoted && !parentheses && !brackets && !braces) {
                addArgument();
                parsedArguments.remainingString = arguments.substring(i + 1).trim();
                return parsedArguments;
            } else {
                currentArgument += char;
            }
        } else if (char === '&' && previousChar === '&') {
            if (!quoted && !parentheses && !brackets && !braces) {
                addArgument();
                parsedArguments.remainingString = arguments.substring(i + 1).trim();
                return parsedArguments;
            } else {
                currentArgument += char;
            }
        } else {
            currentArgument += char;
        }
    }

    if (currentArgument) {
        addArgument();
    }

    return parsedArguments;
};

var verbosity = {
    minimal: 0,
    normal: 1,
    verbose: 2
};

module.exports = watcher;

if (process.env.testing) {
    module.exports.test = {
        parseCommands: parseCommands,
        parseCommand: parseCommand,
        parseArguments: parseArguments,
        spawnCommands: spawnCommands,
        setupCommandsOnStart: setupCommandsOnStart,
        setupCommandsOnChange: setupCommandsOnChange,
        ensureConfigValues: ensureConfigValues
    };
}
<commit_msg>Allow config object instead of specifying file<commit_after>var spawn = require('child_process').spawn;
var spawnSync = require('child_process').spawnSync;
var path = require('path');
var chokidarLib = require('chokidar');
var colors = require('colors/safe');
var configPath = '';
var spawnedCommands = [];
var chokidar = null;
var config = {};

RegExp.prototype.toJSON = RegExp.prototype.toString;
Function.prototype.toJSON = function() {
    return this.toString().replace(/\n/g, '');
};

var watcher = function(initialConfig) {
    var noConfigFound = false;

    initialConfig = initialConfig || path.resolve(process.cwd(), 'watcherConfig.js');

    if (initialConfig.constructor === String) {
        var resolvedPath = path.resolve(initialConfig);
        configPath = resolvedPath;

        try {
            config = require(resolvedPath);
        } catch (e) {
            config = {};
            configPath = '';
            noConfigFound = true;
        }
    } else {
        config = initialConfig;
    }

    ensureConfigValues();

    if (noConfigFound) {
        console.log(colors.yellow('Couldn\'t find the config file »' + resolvedPath + '«. Will use default values.'));
        console.log(colors.yellow('Default config values are:'));
        console.log(JSON.stringify(config, null, 2));
        console.log();
    } else {
        if (config.verbosity >= verbosity.normal) {
            console.log(colors.green('Using following config (' + configPath + '):'));
            console.log(JSON.stringify(config, null, 2));
            console.log();
        }
    }

    if (!config.commandsOnChange.length) {
        config.commandsOnChange.push({
            command: ''
        });
    }

    setupCommandsOnStart();
    setupCommandsOnChange();

    process.on('SIGTERM', (function(self) {
        return function() {
            for (var index in spawnedCommands) {
                var command = spawnedCommands[index];

                if (command) {
                    command.kill('SIGINT');
                }
            }

            if (chokidar) {
                chokidar.close();
            }

            for (var index in config.commandsOnEnd) {
                spawnCommands(config.commandsOnEnd[index].command, true);
            }
        };
    })(this));

};

var ensureConfigValues = function() {
    config.directory = config.directory || process.cwd();
    config.delay = config.delay || 1000;
    config.ignore = config.ignore || [];
    config.verbosity = config.verbosity || 'normal';
    config.verbosity = verbosity[config.verbosity];
    config.commandsOnStart = config.commandsOnStart || [];
    config.commandsOnChange = config.commandsOnChange || [];
    config.commandsOnEnd = config.commandsOnEnd || [];
};

var setupCommandsOnStart = function() {
    for (var index in config.commandsOnStart) {
        if (config.commandsOnStart[index].command.constructor === String) {
            var delay = config.commandsOnStart[index].delay || 0;

            setTimeout(function(index) {
                var commands = spawnCommands(config.commandsOnStart[index].command);
                Array.prototype.push.apply(spawnedCommands, commands);
            }, delay, index);
        } else if (config.commandsOnStart[index].command.constructor === Function) {
            var callbackResult = config.commandsOnStart[index].command();

            var delay = config.commandsOnStart[index].delay || 0;

            setTimeout(function(callbackResult) {
                var commands = spawnCommands(callbackResult);
                Array.prototype.push.apply(spawnedCommands, commands);
            }, delay, callbackResult);
        }
    }
};

var setupCommandsOnChange = function() {
    var timeoutID = 0;
    var allEvents = [];

    if (config.commandsOnChange.length) {
        var directoryToWatch = config.directory.indexOf('/') !== 0 ? path.resolve(process.cwd(), config.directory) : config.directory;

        chokidar = chokidarLib.watch(directoryToWatch, {
            ignoreInitial: true,
            ignored: config.ignore
        });

        chokidar.on('all', function(event, path) {
            clearTimeout(timeoutID);
            allEvents.push([event, path]);

            if (configPath && event === 'change' && configPath === path) { // The config file changed
                delete require.cache[configPath];
                config = require(configPath);
                ensureConfigValues();
                if (config.verbosity >= verbosity.normal) {
                    console.log(colors.green('Config file changed, reloaded it. The new values are:'));
                    console.log(JSON.stringify(config, null, 2));
                    console.log();
                }
                allEvents = [];
                return;
            }

            timeoutID = setTimeout(function() {
                if (config.verbosity >= verbosity.verbose) {
                    console.log(colors.green('Executing callbacks for the following events:'));
                    console.log(JSON.stringify(allEvents, null, 2));
                    console.log();
                }

                var commandsWereSpawned = false;

                for (var index in config.commandsOnChange) {
                    if (config.commandsOnChange[index].command.constructor === String) {
                        var delay = config.commandsOnChange[index].delay || 0;

                        setTimeout(function(index) {
                            spawnCommands(config.commandsOnChange[index].command);
                        }, delay, index);

                        commandsWereSpawned = config.commandsOnChange[index].command !== '';
                    } else if (config.commandsOnChange[index].command.constructor === Function) {
                        for (var i = 0; i < allEvents.length; i++) {
                            var event = allEvents[i][0];
                            var file = allEvents[i][1];
                            var callbackResult = config.commandsOnChange[index].command(event, file);

                            var delay = config.commandsOnChange[index].delay || 0;

                            setTimeout(function(callbackResult) {
                                spawnCommands(callbackResult);
                            }, delay, callbackResult);

                            commandsWereSpawned = callbackResult !== '';
                        }
                    }
                }

                if (!commandsWereSpawned) {
                    if (config.verbosity >= verbosity.verbose) {
                        console.log(colors.yellow('No callbacks were called.'));
                        console.log();
                    }
                }

                allEvents = [];
            }, config.delay);
        });
    }
};

var spawnCommands = function(commandString, sync) {
    var parsedCommands = parseCommands(commandString);
    var spawnedCommands = [];

    for (var index in parsedCommands) {
        var parsedCommand = parsedCommands[index];

        if (config.verbosity >= verbosity.normal) {
            console.log(colors.green('Spawning the command') + ' "' + parsedCommand.command + '" ' + colors.green('with the arguments ') + JSON.stringify(parsedCommand.arguments));
            console.log();
        }

        var child;

        if (!sync) {
            child = spawn(parsedCommand.command, parsedCommand.arguments, {
                stdio: 'inherit'
            });

            var onExit = (function(parsedCommand) {
                return function() {
                    if (config.verbosity >= verbosity.normal) {
                        console.log(colors.green('The command') + ' "' + parsedCommand.command + '" ' + colors.green('with arguments ') + JSON.stringify(parsedCommand.arguments) + colors.green(' exited'));
                        console.log();
                    }
                };
            })(parsedCommand);

            child.on('exit', onExit);
            child.on('SIGTERM', onExit);
            child.on('SIGINT', onExit);
        } else {
            spawnSync(parsedCommand.command, parsedCommand.arguments, {
                stdio: 'inherit'
            });
        }

        spawnedCommands.push(child);
    }

    return spawnedCommands;
};

var parseCommands = function(commandString) {
    var commands = [];

    if (commandString) {
        var parsedCommand = parseCommand(commandString);

        commands.push({
            command: parsedCommand.command,
            arguments: parsedCommand.arguments
        });

        Array.prototype.push.apply(commands, parseCommands(parsedCommand.remainingString));
    }

    return commands;
};

var parseCommand = function(command) {
    var quoted = false;
    var parsedCommand = {
        command: '',
        arguments: [],
        remainingString: ''
    };

    if (!command) {
        return parsedCommand;
    }

    var checkForArguments = function() {
        var remainingString = command.replace(parsedCommand.command, '').trim();

        if (remainingString.indexOf(';') === 0) {
            parsedCommand.remainingString = remainingString.substring(1).trim();
            return parsedCommand;
        }

        var parsedArguments = parseArguments(remainingString);

        parsedCommand.arguments = parsedArguments.arguments;
        parsedCommand.remainingString = parsedArguments.remainingString;
        return parsedCommand;
    };

    for (var i = 0; i < command.length; i++) {
        var char = command.charAt(i);
        var previousChar = i > 0 ? command.charAt(i - 1) : '';

        if (char === ' ') {
            if (quoted || previousChar === '\\') {
                parsedCommand.command += char;
            } else {
                return checkForArguments();
            }
        } else if (char === '"' || char === '\'') {
            if (quoted) {
                return checkForArguments();
            }

            quoted = true;
        } else if (char === ';' && !quoted) {
            return checkForArguments();
        } else if (char === '&' && previousChar === '&' && !quoted) {
            return checkForArguments();
        } else {
            parsedCommand.command += char;
        }
    }

    return checkForArguments();
};

var parseArguments = function(arguments) {
    var quoted = false;
    var parentheses = 0;
    var brackets = 0;
    var braces = 0;
    var parsedArguments = {
        arguments: [],
        remainingString: ''
    };

    if (!arguments) {
        return parsedArguments;
    }

    var currentArgument = '';

    var addArgument = function() {
        currentArgument = currentArgument.trim();

        if (currentArgument.indexOf('"') === 0 && currentArgument.match(/"$/)) {
            currentArgument = currentArgument.substring(1, currentArgument.length - 1);
        }

        if (currentArgument !== '' && currentArgument !== '&') {
            parsedArguments.arguments.push(currentArgument);
        }

        currentArgument = '';
        quoted = false;
        var parentheses = 0;
        var brackets = 0;
        var braces = 0;
    };

    for (var i = 0; i < arguments.length; i++) {
        var char = arguments[i];
        var previousChar = i > 0 ? arguments.charAt(i - 1) : '';

        if (char === ' ') {
            if (quoted || previousChar === '\\') {
                currentArgument += char;
            } else {
                addArgument();
            }
        } else if (char === '"' || char === '\'') {
            currentArgument += char;

            if (quoted && !parentheses && !brackets && !braces) {
                addArgument();
            } else if (quoted) {
                quoted = false;
            } else {
                quoted = true;
            }
        } else if (char === '(') {
            currentArgument += char;
            parentheses++;

        } else if (char === ')') {
            currentArgument += char;
            parentheses--;

            if (!parentheses && !brackets && !braces && !quoted) {
                addArgument();
            }
        } else if (char === '[') {
            currentArgument += char;
            brackets++;
        } else if (char === ']') {
            currentArgument += char;
            brackets--;

            if (!parentheses && !brackets && !braces && !quoted) {
                addArgument();
            }
        } else if (char === '{') {
            currentArgument += char;
            braces++;
        } else if (char === '}') {
            currentArgument += char;
            braces--;

            if (!parentheses && !brackets && !braces && !quoted) {
                addArgument();
            }
        } else if (char === ';') {
            if (!quoted && !parentheses && !brackets && !braces) {
                addArgument();
                parsedArguments.remainingString = arguments.substring(i + 1).trim();
                return parsedArguments;
            } else {
                currentArgument += char;
            }
        } else if (char === '&' && previousChar === '&') {
            if (!quoted && !parentheses && !brackets && !braces) {
                addArgument();
                parsedArguments.remainingString = arguments.substring(i + 1).trim();
                return parsedArguments;
            } else {
                currentArgument += char;
            }
        } else {
            currentArgument += char;
        }
    }

    if (currentArgument) {
        addArgument();
    }

    return parsedArguments;
};

var verbosity = {
    minimal: 0,
    normal: 1,
    verbose: 2
};

module.exports = watcher;

if (process.env.testing) {
    module.exports.test = {
        parseCommands: parseCommands,
        parseCommand: parseCommand,
        parseArguments: parseArguments,
        spawnCommands: spawnCommands,
        setupCommandsOnStart: setupCommandsOnStart,
        setupCommandsOnChange: setupCommandsOnChange,
        ensureConfigValues: ensureConfigValues
    };
}
<|endoftext|>


<commit_before>var
    Promise = require('bluebird'),
    fs = require('fs'),
    http = require('http'),
    https = require('https'),
	path = require('path'),
    express = require('express'),
    session = require('express-session'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    cookieParser = require('cookie-parser'),
    lessMiddleware = require('less-middleware'),
    request = require('request'),
    uuid = require('node-uuid'),

    console = require('clim')(),

    config = (require('../../../config').cn || { }),
    httpConfig = (config ? config.http : { });

if (config.auth) {
    var maybeAddSlashTo = (s) => s.slice(-1) == '/' ? s : s + '/';
    config.auth.path = maybeAddSlashTo(config.auth.path);

    if (!config.auth.secret) config.auth.secret = uuid.v4();

    if (typeof config.auth.cacheMinutes == 'number') {
        config.auth.cacheMinutes = {
            good: config.auth.cacheMinutes,
            bad: config.auth.cacheMinutes
        };
    }

    if (!config.auth.cacheMinutes.good) config.auth.cacheMinutes.good = 1;
    if (!config.auth.cacheMinutes.bad)  config.auth.cacheMinutes.bad  = 1;
}

http.globalAgent.maxSockets = 1024;

(function() {
    'use strict';

    var HttpHandler = function(controller, port, securePort, creds) {
        this.controller = controller;
        this.port = port;
        this.securePort = securePort;
        this.creds = creds;
    }

    HttpHandler.prototype.start = function(creds) {
        var app = express();

        app.use(morgan('dev'));
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: true }));

        app.use(function(req, res, next) {
            if (config.auth) {
                res.header(
                    'Access-Control-Allow-Headers',
                    'Content-Type, Authorization');
                res.header(
                    'Access-Control-Allow-Methods',
                    'GET, OPTIONS');
            }

            Object.keys(httpConfig.headers).map(function(key) {
                res.header(key, httpConfig.headers[key]);
            });
            res.header('X-powered-by', 'Hobu, Inc.');
            next();
        });

        if (httpConfig.enableStaticServe) this.registerStatic(app);
        this.registerCommands(app);

        if (this.port) {
            http.createServer(app).listen(this.port);
            console.log('HTTP server running on port', this.port);

            if (config.auth) {
                console.warn('Secure cookies disabled since HTTP is enabled');
                console.log('Set http.port to null for secure cookies');
            }
        }

        if (this.securePort) {
            https.createServer(this.creds, app).listen(this.securePort);
            console.log('HTTPS server running on port', this.securePort);
        }
    }

    HttpHandler.prototype.registerStatic = function(app) {
        app.set('views', __dirname + '/static/views');
        app.set('view engine', 'jade');

        var publicDir = '/static/public';
        app.use(lessMiddleware(path.join(__dirname, publicDir)));
        app.use(express.static(__dirname + publicDir));

        app.get('/ws/:resourceId', function(req, res) {
            res.render('wsView');
        });

        app.get('/http/:resourceId', function(req, res) {
            res.render('httpView');
        });
    };

    HttpHandler.prototype.registerCommands = function(app) {
        var controller = this.controller;

        if (config.auth) {
            console.log('Proxying auth requests to', config.auth.path);
            var auths = { };

            app.options('*', function(req, res) {
                res.status(200).end();
            });

            app.use('/resource/:resource(*)/:call(info|read)',
                    function(req, res, next)
            {
                var jar = config.auth.signed ? req.signedCookies : req.cookies;
                var id = jar[config.auth.cookieName];

                if (!id) res.status(401).send();

                var resource = req.params.resource;

                if (!auths[id]) auths[id] = { };

                if (!auths[id][resource]) {
                    console.log('Authing', id);
                    var start = new Date();

                    auths[id][resource] = new Promise((resolve, reject) => {
                        var options = {
                            url: config.auth.path + resource,
                            rejectUnauthorized: false
                        };

                        req.pipe(request(options, (err, authResponse) => {
                            if (err) {
                                console.log('Auth proxy err:', err);
                                return reject(500);
                            }

                            var code = authResponse.statusCode;
                            var ok = Math.floor(code / 100) == 2;

                            var time = (new Date() - start) / 1000;

                            console.log('Authed', id, 'in', time, 's:', ok);

                            var timeoutMinutes = ok ?
                                config.auth.cacheMinutes.good :
                                config.auth.cacheMinutes.bad;

                            setTimeout(() => {
                                delete auths[id][resource];
                                if (!Object.keys(auths[id]).length) {
                                    delete auths[id];
                                }
                            }, timeoutMinutes * 60000);

                            if (ok) resolve();
                            else reject(code);
                        }));
                    });
                }

                auths[id][resource].then(() => next())
                .catch((statusCode) => res.status(statusCode).send());
            });
        }

        app.get('/resource/:resource(*)/info', function(req, res) {
            controller.info(req.params.resource, function(err, data) {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });

        app.get('/resource/:resource(*)/read', function(req, res) {
            // Terminate query on socket hangup.
            var keepGoing = true;
            req.on('close', () => keepGoing = false);

            controller.read(
                req.params.resource,
                req.query,
                function(err) {
                    if (err) return res.json(err.code || 500, err.message);
                    res.header('Content-Type', 'application/octet-stream');
                },
                function(err, data, done) {
                    if (err) {
                        console.error('Encountered data error');
                        return res.status(err.code || 500).json(err.message);
                    }

                    res.write(data);
                    if (done) res.end();

                    return keepGoing;
                }
            );
        });

        app.get('/resource/:resource(*)/hierarchy', function(req, res) {
            var resource = req.params.resource;
            var query = req.query;

            controller.hierarchy(resource, query, (err, data) => {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });
    }

    module.exports.HttpHandler = HttpHandler
})();

<commit_msg>Add middleware.<commit_after>var
    Promise = require('bluebird'),
    fs = require('fs'),
    http = require('http'),
    https = require('https'),
	path = require('path'),
    express = require('express'),
    session = require('express-session'),
    morgan = require('morgan'),
    bodyParser = require('body-parser'),
    cookieParser = require('cookie-parser'),
    lessMiddleware = require('less-middleware'),
    request = require('request'),
    uuid = require('node-uuid'),

    console = require('clim')(),

    config = (require('../../../config').cn || { }),
    httpConfig = (config ? config.http : { });

if (config.auth) {
    var maybeAddSlashTo = (s) => s.slice(-1) == '/' ? s : s + '/';
    config.auth.path = maybeAddSlashTo(config.auth.path);

    if (!config.auth.secret) config.auth.secret = uuid.v4();

    if (typeof config.auth.cacheMinutes == 'number') {
        config.auth.cacheMinutes = {
            good: config.auth.cacheMinutes,
            bad: config.auth.cacheMinutes
        };
    }

    if (!config.auth.cacheMinutes.good) config.auth.cacheMinutes.good = 1;
    if (!config.auth.cacheMinutes.bad)  config.auth.cacheMinutes.bad  = 1;
}

http.globalAgent.maxSockets = 1024;

(function() {
    'use strict';

    var HttpHandler = function(controller, port, securePort, creds) {
        this.controller = controller;
        this.port = port;
        this.securePort = securePort;
        this.creds = creds;
    }

    HttpHandler.prototype.start = function(creds) {
        var app = express();

        app.use(morgan('dev'));
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: true }));
        app.use(cookieParser);

        app.use(function(req, res, next) {
            if (config.auth) {
                res.header(
                    'Access-Control-Allow-Headers',
                    'Content-Type, Authorization');
                res.header(
                    'Access-Control-Allow-Methods',
                    'GET, OPTIONS');
            }

            Object.keys(httpConfig.headers).map(function(key) {
                res.header(key, httpConfig.headers[key]);
            });
            res.header('X-powered-by', 'Hobu, Inc.');
            next();
        });

        if (httpConfig.enableStaticServe) this.registerStatic(app);
        this.registerCommands(app);

        if (this.port) {
            http.createServer(app).listen(this.port);
            console.log('HTTP server running on port', this.port);

            if (config.auth) {
                console.warn('Secure cookies disabled since HTTP is enabled');
                console.log('Set http.port to null for secure cookies');
            }
        }

        if (this.securePort) {
            https.createServer(this.creds, app).listen(this.securePort);
            console.log('HTTPS server running on port', this.securePort);
        }
    }

    HttpHandler.prototype.registerStatic = function(app) {
        app.set('views', __dirname + '/static/views');
        app.set('view engine', 'jade');

        var publicDir = '/static/public';
        app.use(lessMiddleware(path.join(__dirname, publicDir)));
        app.use(express.static(__dirname + publicDir));

        app.get('/ws/:resourceId', function(req, res) {
            res.render('wsView');
        });

        app.get('/http/:resourceId', function(req, res) {
            res.render('httpView');
        });
    };

    HttpHandler.prototype.registerCommands = function(app) {
        var controller = this.controller;

        if (config.auth) {
            console.log('Proxying auth requests to', config.auth.path);
            var auths = { };

            app.options('*', function(req, res) {
                res.status(200).end();
            });

            app.use('/resource/:resource(*)/:call(info|read)',
                    function(req, res, next)
            {
                var jar = config.auth.signed ? req.signedCookies : req.cookies;
                var id = jar[config.auth.cookieName];

                if (!id) res.status(401).send();

                var resource = req.params.resource;

                if (!auths[id]) auths[id] = { };

                if (!auths[id][resource]) {
                    console.log('Authing', id);
                    var start = new Date();

                    auths[id][resource] = new Promise((resolve, reject) => {
                        var options = {
                            url: config.auth.path + resource,
                            rejectUnauthorized: false
                        };

                        req.pipe(request(options, (err, authResponse) => {
                            if (err) {
                                console.log('Auth proxy err:', err);
                                return reject(500);
                            }

                            var code = authResponse.statusCode;
                            var ok = Math.floor(code / 100) == 2;

                            var time = (new Date() - start) / 1000;

                            console.log('Authed', id, 'in', time, 's:', ok);

                            var timeoutMinutes = ok ?
                                config.auth.cacheMinutes.good :
                                config.auth.cacheMinutes.bad;

                            setTimeout(() => {
                                delete auths[id][resource];
                                if (!Object.keys(auths[id]).length) {
                                    delete auths[id];
                                }
                            }, timeoutMinutes * 60000);

                            if (ok) resolve();
                            else reject(code);
                        }));
                    });
                }

                auths[id][resource].then(() => next())
                .catch((statusCode) => res.status(statusCode).send());
            });
        }

        app.get('/resource/:resource(*)/info', function(req, res) {
            controller.info(req.params.resource, function(err, data) {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });

        app.get('/resource/:resource(*)/read', function(req, res) {
            // Terminate query on socket hangup.
            var keepGoing = true;
            req.on('close', () => keepGoing = false);

            controller.read(
                req.params.resource,
                req.query,
                function(err) {
                    if (err) return res.json(err.code || 500, err.message);
                    res.header('Content-Type', 'application/octet-stream');
                },
                function(err, data, done) {
                    if (err) {
                        console.error('Encountered data error');
                        return res.status(err.code || 500).json(err.message);
                    }

                    res.write(data);
                    if (done) res.end();

                    return keepGoing;
                }
            );
        });

        app.get('/resource/:resource(*)/hierarchy', function(req, res) {
            var resource = req.params.resource;
            var query = req.query;

            controller.hierarchy(resource, query, (err, data) => {
                if (err) return res.status(err.code || 500).json(err.message);
                else return res.json(data);
            });
        });
    }

    module.exports.HttpHandler = HttpHandler
})();

<|endoftext|>


<commit_before>function Option(aliases, description, name, def) {
    var opt = {};

    if (aliases.constructor === Object) {
        opt = aliases;
    } else {
        opt.aliases = aliases;
        opt.value = null;
        opt.def = def;
        opt.description = description;
        opt.name = name;
    }

    if (typeof opt.aliases === "string") {
        opt.aliases = [opt.aliases];
    }

    opt.aliases.forEach(function (c, i) {
        opt.aliases[i] = c.length === 1 ? "-" + c : ("--" + c);
    });

    opt.is_provided = false;

    return opt;
}

module.exports = Option;
<commit_msg>Sort the single letter aliases first<commit_after>function Option(aliases, description, name, def) {
    var opt = {};

    if (aliases.constructor === Object) {
        opt = aliases;
    } else {
        opt.aliases = aliases;
        opt.value = null;
        opt.def = def;
        opt.description = description;
        opt.name = name;
    }

    if (typeof opt.aliases === "string") {
        opt.aliases = [opt.aliases];
    }

    opt.aliases.forEach(function (c, i) {
        opt.aliases[i] = c.length === 1 ? "-" + c : ("--" + c);
    });

    opt.aliases.sort(function (a, b) {
        return a.length === 2 ? -1 : 1;
    });

    opt.is_provided = false;

    return opt;
}

module.exports = Option;
<|endoftext|>