<commit_before>'use strict';

const config = require('config');
const async = require('async');
const CronJob = require('cron').CronJob;
const Twit = require('twit');
const rg = require('rangen');
const request = require('request').defaults({ encoding: null });
// const Rx = require('rx');

let Bot = new Twit({
  consumer_key: config.TWITTER_CONSUMER_KEY,
  consumer_secret: config.TWITTER_CONSUMER_SECRET,
  access_token: config.TWITTER_ACCESS_TOKEN,
  access_token_secret: config.TWITTER_ACCESS_TOKEN_SECRET
});

new CronJob('0 */25 * * * *', createFreshTweet, null, true, 'America/Los_Angeles');
// new CronJob('0 */25 * * * *', createPopularTweet, null, true, 'America/Los_Angeles');

// function createPopularTweet() {
//   async.waterfall([
//     getPopularPhoto,
//     //     generateStatus,
//     //     createBuffer,
//     //     uploadMedia,
//     //     postTweet
//   ], (err, result) => {
//     if (err) {
//       return console.error('popular image tweet error', err);
//     }
//     console.log('popular image tweet posted on: ', new Date());
//   });
// }

function createFreshTweet() {
  async.waterfall([
    getFreshPhoto,
    generateStatus,
    createBuffer,
    uploadMedia,
    postTweet
  ], (err, result) => {
    if (err) {
      return console.error('fresh image tweet error', err);
    }
    console.log('fresh image tweet posted on: ', new Date());
  });
}

function getFreshPhoto(cb) {
  let options = {};
  rg.image({
    image_size: 4,
    feature: 'fresh_today',
    tags: 1
  }, (err, images) => {
    if (err) {
      return cb(err);
    }
    let imgs = filter(images);

    if (!imgs.length) {
      return setTimeout(() => {
        console.log('fresh foto: trying more...');
        getFreshPhoto(cb);
      }, 5000);
    }
    options.image = imgs[0];
    cb(null, options);
  });
}

// let popular = [];
// function getPopularPhoto(cb) {
//   let options = {};

//   if (popular.length) {
//     options.image = popular[0];
//     popular.splice(0, 1);
//     return cb(null, options);
//   }

//   rg.image({
//     image_size: 4,
//     feature: 'popular',
//     tags: 1,
//     rpp: 40
//   }, (err, images) => {
//     if (err) {
//       return cb(err);
//     }
//     let imgs = filter(images);

//     if (!imgs.length) {
//       return setTimeout(() => {
//         console.log('popular foto: trying more...');
//         getPopularPhoto(cb);
//       }, 5000);
//     }
//     options.image = imgs[0];
//     imgs.splice(0, 1);
//     Array.prototype.push.apply(popular, imgs);
//     cb(null, options);
//   });
// }

function generateStatus(options, cb) {
  let status = options.image.description;

  options.image.tags.forEach(t => {
    if ((status.length + t.length < 115) && (t.search(/&amp/i) === -1)) {
      status += ` #${t}`;
    }
  });

  if (status.length < 117 && options.image.camera) {
    if (status.length + options.image.camera < 115) {
      status += ` #${options.image.camera}`;
    }
  }

  options.status = status;
  cb(null, options);
}

function createBuffer(options, cb) {
  request.get(options.image.image_url, (error, response, body) => {
    if (error || response.statusCode !== 200) {
      return cb('createBuffer: error receiving remote image');
    }
    options.b64content = new Buffer(body).toString('base64');
    cb(null, options);
  });
}

function uploadMedia(options, cb) {
  Bot.post('media/upload', { media_data: options.b64content }, (err, data, response) => {
    if (err) {
      return cb(err);
    }
    options.mediaIdStr = data.media_id_string;
    cb(null, options);
  });
}

function postTweet(options, cb) {
  let status = options.status;
  console.log('status.length: ', status.length);
  if (status.length > 116) {
    status = status.substring(0, 116);
  }
  console.log('status.length: ', status.length);
  let params = {
    status,
    media_ids: [options.mediaIdStr]
  };
  Bot.post('statuses/update', params, (err, data, response) => {
    if (err) {
      return cb(err);
    }
    cb(null);
  });
}

function filter(array) {
  return array
    .filter(i => i.tags.length > 2)
    .filter(i => i.description && i.name)
    .filter(i => i.description.length > 10 && i.description.length < 90)
    .filter(i => i.name.length > 4 && i.name.length < 90)
    .filter(i => i.description.toLowerCase() !== 'untitled')
    .filter(i => i.name.toLowerCase() !== 'untitled')
    .filter(i => i.description.search(/.jpg/i) === -1)
    .filter(i => i.name.search(/.jpg/i) === -1)
    .filter(i => i.description.search(/http:/i) === -1)
    .filter(i => i.description.search(/https:/i) === -1)
    .filter(i => i.name.search(/russia/i) === -1)
    .filter(i => i.description.search(/russia/i) === -1);
}
<commit_msg>add popular photos<commit_after>'use strict';

const config = require('config');
const async = require('async');
const CronJob = require('cron').CronJob;
const Twit = require('twit');
const rg = require('rangen');
const request = require('request').defaults({ encoding: null });
// const Rx = require('rx');

let Bot = new Twit({
  consumer_key: config.TWITTER_CONSUMER_KEY,
  consumer_secret: config.TWITTER_CONSUMER_SECRET,
  access_token: config.TWITTER_ACCESS_TOKEN,
  access_token_secret: config.TWITTER_ACCESS_TOKEN_SECRET
});

new CronJob('0 */55 * * * *', createTweet, null, true, 'America/Los_Angeles');

let photos = [];

function createTweet() {
  async.waterfall([
    getPhoto,
    generateStatus,
    createBuffer,
    uploadMedia,
    postTweet
  ], (err, result) => {
    if (err) {
      return console.error('tweet posting error', err);
    }
    console.log('tweet posted on: ', new Date());
  });
}

function getPhoto(cb) {
  let options = {};
  if (photos.length) {
    options.image = photos[0];
    photos.splice(0, 1);
    if (!photos.length) {
      findPhotos();
    }
    return cb(null, options);
  }
  findPhotos();
  cb('no photos');
}

function findPhotos() {
  async.parallel([
    pxPopular,
    pxFresh,
  ], err => {
    if (err) {
      return console.error('find photos error', err);
    }
    photos = shuffle(photos);
    console.log(photos.length, 'received');
    if (photos.length < 26) {
      getMorePhotos();
    }
  });

  function pxPopular(cb) {
    rg.image({
      image_size: 4,
      feature: 'popular',
      tags: 1,
      rpp: 100
    }, (err, images) => {
      if (err) {
        return cb(err);
      }
      let imgs = filter(images);

      Array.prototype.push.apply(photos, imgs);
      cb(null);
    });
  }

  function pxFresh(cb) {
    rg.image({
      image_size: 4,
      feature: 'fresh_today',
      tags: 1,
      rpp: 100
    }, (err, images) => {
      if (err) {
        return cb(err);
      }
      let imgs = filter(images);

      Array.prototype.push.apply(photos, imgs);
      cb(null);
    });
  }

  function getMorePhotos() {
    console.log('get more photos');
  }
}

function generateStatus(options, cb) {
  let status = options.image.description;

  let tags = filterTags(options.image.tags);
  tags.forEach(t => {
    if ((status.length + t.length < 115) && (t.search(/&amp/i) === -1)) {
      status += ` #${t}`;
    }
  });

  if (status.length < 117 && options.image.camera) {
    if (status.length + options.image.camera < 115) {
      status += ` #${options.image.camera}`;
    }
  }

  options.status = status;
  cb(null, options);
}

function createBuffer(options, cb) {
  request.get(options.image.image_url, (error, response, body) => {
    if (error || response.statusCode !== 200) {
      return cb('createBuffer: error receiving remote image');
    }
    options.b64content = new Buffer(body).toString('base64');
    cb(null, options);
  });
}

function uploadMedia(options, cb) {
  Bot.post('media/upload', { media_data: options.b64content }, (err, data, response) => {
    if (err) {
      return cb(err);
    }
    options.mediaIdStr = data.media_id_string;
    cb(null, options);
  });
}

function postTweet(options, cb) {
  let status = options.status;
  if (status.length > 116) {
    status = status.substring(0, 116);
  }
  let params = {
    status,
    media_ids: [options.mediaIdStr]
  };
  Bot.post('statuses/update', params, (err, data, response) => {
    if (err) {
      return cb(err);
    }
    cb(null);
  });
}

function filter(images) {
  return images
    .filter(i => i.tags.length > 2)
    .filter(i => i.description && i.name)
    .filter(i => i.description.length > 10 && i.description.length < 90)
    .filter(i => i.name.length > 4 && i.name.length < 90)
    .filter(i => i.description.toLowerCase() !== 'untitled')
    .filter(i => i.name.toLowerCase() !== 'untitled')
    .filter(i => i.description.search(/.jpg/i) === -1)
    .filter(i => i.name.search(/.jpg/i) === -1)
    .filter(i => i.description.search(/http:/i) === -1)
    .filter(i => i.description.search(/https:/i) === -1)
    .filter(i => i.name.search(/russia/i) === -1)
    .filter(i => i.description.search(/russia/i) === -1);
}

function filterTags(tags) {
  return tags
    .filter(i => i.length > 1)
    .filter(i => i.search(/russia/i) === -1)
    .filter(i => i.search(/moscow/i) === -1);
}

function shuffle(o) {
  for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); //eslint-disable-line
  return o;
}
<|endoftext|>


<commit_before>/**
 * Storage agnostic LRU list w/ async value IO
 *
 * Based on https://github.com/rsms/js-lru
 *   Licensed under MIT.
 *   Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
 *
 * Additions to rsms/js-lru:
 *   Licensed under MIT.
 *   Copyright (c) 2013 David Smith <https://github.com/codeactual/>
 *
 * Original illustration of the design from rsms/js-lru:
 *
 *    entry             entry             entry             entry
 *    ______            ______            ______            ______
 *   | head |.newer => |      |.newer => |      |.newer => | tail |
 *   |  A   |          |  B   |          |  C   |          |  D   |
 *   |______| <= older.|______| <= older.|______| <= older.|______|
 *
 *    removed  <--  <--  <--  <--  <--  <--  <--  <--  <--  added
 */
module.exports = {
  LRUList: LRUList,
  LRUEntry: LRUEntry
};

var Batch = require('batch');

var emptyFn = function() {};

/**
 * @param {object} config
 *   {number} [limit=100]
 *   {function} set(key, val, done)
 *     done(err)
 *   {function} get(key, done)
 *     done(err, val)
 *   {function} del(key, done)
 *     done(err)
 *
 * done() callbacks:
 *   'err' should be an Error instance.
 *   List structures will not be modified on truthy 'err'.
 */
function LRUList(config) {
  config = config || {};
  this.size = 0;
  this.tail = undefined;
  this.head = undefined;
  this.limit = config.limit || 100;
  this.store = {
    set: config.set || emptyFn,
    setMulti: config.setMulti || emptyFn,
    get: config.get || emptyFn,
    getMulti: config.getMulti || emptyFn,
    del: config.del || emptyFn,
    delMulti: config.delMulti || emptyFn
  };
  this.keymap = {};
}

function LRUEntry(key) {
  this.key = key;
  this.older = undefined;
  this.newer = undefined;
}

LRUList.prototype._updateStructForPut = function(key, val, done) {
  var entry = new LRUEntry(key); // Create new tail.
  this.keymap[key] = entry;

  if (this.tail) { // Link old tail to new tail.
    this.tail.newer = entry;
    entry.older = this.tail;
  } else { // First entry.
    this.head = entry;
  }

  this.tail = entry; // Assign new tail.

  if (this.size === this.limit) { // List size exceeded. Trim head.
    this.shift(done);
  } else {
    this.size++;
    done(null);
  }
};

LRUList.prototype.putMulti = function(pairs, done) {
  done = done || function putMultiDoneNoOp() {};
  var self = this;

  this.store.setMulti(pairs, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.

    var batch = new Batch;

    Object.keys(pairs).forEach(function batchKey(key) {
      batch.push(function batchPush(taskDone) {
        self._updateStructForPut(key, pairs[key], taskDone);
      });
    });

    batch.end(function batchEnd(err) {
      done(err);
    });
  });
};

/**
 * Append key to the list's tail. Trigger storage of the value.
 *
 * - Duplicate keys are allowed by original design.
 *   May produce "orphaned" entries to which the key map no longer points. Then they
 *   can no longer be read/removed, and can only be pushed out by lack of use.
 *
 * @param {string} key
 * @param {mixed} val
 * @param {function} done
 *   {object} Error instance or null.
 */
LRUList.prototype.put = function(key, val, done) {
  done = done || function putDoneNoOp() {};
  var self = this;
  this.store.set(key, val, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    self._updateStructForPut(key, val, done);
  });
};

/**
 * Remove the key at the list's head (the LRU). Trigger removal of the value.
 *
 * @param {function} done
 *   {object} Error instance or null.
 *   {mixed} Shifted LRUEntry or undefined.
 */
LRUList.prototype.shift = function(done) {
  done = done || function shiftDoneNoOp() {};
  var self = this;

  var entry = this.head;
  if (!entry) { // List already empty.
    done(null);
    return;
  }

  this.store.del(entry.key, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.

    if (self.head.newer) { // 2nd-to-head is now head.
      self.head = self.head.newer;
      self.head.older = undefined;
    } else { // Head was the only entry.
      self.head = undefined;
    }

    // Remove last strong reference to <entry> and remove links from the purged
    // entry being returned.
    entry.newer = entry.older = undefined;

    delete self.keymap[entry.key];
    done(null, entry);
  });
};

LRUList.prototype._updateStructForGet = function(entry) {
  if (entry.newer) { // Key has more-recently-used than it.
    if (entry === this.head) {
      this.head = entry.newer; // 2nd-to-head is now head.
    }
    // Connect adjacent entries to fill the future gap it will leave.
    entry.newer.older = entry.older;
  }
  if (entry.older) { // Key has less-recently-used than it.
    // Connect adjacent entries to fill the future gap it will leave.
    entry.older.newer = entry.newer;
  }

  entry.newer = undefined; // Entry will be newest.

  // Move current tail will 2nd-to-tail positiion.
  entry.older = this.tail;
  if (this.tail) { this.tail.newer = entry; }

  this.tail = entry;
};

LRUList.prototype.getMulti = function(keys, done) {
  done = done || function getMultiDoneNoOp() {};
  var self = this;

  this.store.getMulti(keys, function storeIODone(err, pairs) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    for (var k = 0; k < keys.length; k++) {
      var entry = self.keymap[keys[k]];
      if (entry === undefined) { continue; } // Key miss.
      if (entry === self.tail) { continue; } // Key already MRU.
      self._updateStructForGet(entry);
    }
    done(null, pairs);
  });
};

/**
 * Promote the key to the tail (MRU). Read the value from storage.
 *
 * @param {string|array} key
 * @param {function} done
 *   {object} Error instance or null.
 *   {mixed} Value or undefined.
 */
LRUList.prototype.get = function(key, done) {
  done = done || function getDoneNoOp() {};
  var self = this;

  this.store.get(key, function storeIODone(err, val) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.

    var entry = self.keymap[key];
    if (entry === undefined) { done(null); return; } // Key miss.
    if (entry === self.tail) { done(null, val); return; } // Key already MRU.

    self._updateStructForGet(entry);
    done(null, val);
  });
};

LRUList.prototype._updateStructForRemove = function(key) {
  var entry = this.keymap[key];
  if (!entry) { return; } // Key miss.

  delete this.keymap[entry.key];

  if (entry.newer && entry.older) {
    // Connect adjacent entries to fill the future gap it will leave.
    entry.older.newer = entry.newer;
    entry.newer.older = entry.older;
  } else if (entry.newer) { // Removing head.
    entry.newer.older = undefined;
    this.head = entry.newer;
  } else if (entry.older) { // Removing tail.
    entry.older.newer = undefined;
    this.tail = entry.older;
  } else { // Removing sole key in list.
    this.head = this.tail = undefined;
  }

  this.size--;
};

LRUList.prototype.removeMulti = function(keys, done) {
  done = done || function removeDoneNoOp() {};
  var self = this;

  this.store.delMulti(keys, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    for (var k = 0; k < keys.length; k++) {
      self._updateStructForRemove(keys[k]);
    }
    done(null);
  });
};

/**
 * Remove the key from the list and key map. Trigger removal of the value.
 *
 * @param {string} key
 * @param {function} done
 *   {object} Error instance or null.
 */
LRUList.prototype.remove = function(key, done) {
  done = done || function removeDoneNoOp() {};
  var self = this;

  this.store.del(key, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    self._updateStructForRemove(key);
    done(null);
  });
}

/**
 * Produce a head-to-tail ordered key list.
 *
 * @return {array}
 */
LRUList.prototype.keys = function() {
  var arr = [];
  var entry = this.head;
  while (entry) {
    arr.push(entry.key);
    entry = entry.newer;
  }
  return arr;
};
<commit_msg>Fix jshint errors<commit_after>/**
 * Storage agnostic LRU list w/ async value IO
 *
 * Based on https://github.com/rsms/js-lru
 *   Licensed under MIT.
 *   Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
 *
 * Additions to rsms/js-lru:
 *   Licensed under MIT.
 *   Copyright (c) 2013 David Smith <https://github.com/codeactual/>
 *
 * Original illustration of the design from rsms/js-lru:
 *
 *    entry             entry             entry             entry
 *    ______            ______            ______            ______
 *   | head |.newer => |      |.newer => |      |.newer => | tail |
 *   |  A   |          |  B   |          |  C   |          |  D   |
 *   |______| <= older.|______| <= older.|______| <= older.|______|
 *
 *    removed  <--  <--  <--  <--  <--  <--  <--  <--  <--  added
 */
module.exports = {
  LRUList: LRUList,
  LRUEntry: LRUEntry
};

var Batch = require('batch');

var emptyFn = function() {};

/**
 * @param {object} config
 *   {number} [limit=100]
 *   {function} set(key, val, done)
 *     done(err)
 *   {function} get(key, done)
 *     done(err, val)
 *   {function} del(key, done)
 *     done(err)
 *
 * done() callbacks:
 *   'err' should be an Error instance.
 *   List structures will not be modified on truthy 'err'.
 */
function LRUList(config) {
  config = config || {};
  this.size = 0;
  this.tail = undefined;
  this.head = undefined;
  this.limit = config.limit || 100;
  this.store = {
    set: config.set || emptyFn,
    setMulti: config.setMulti || emptyFn,
    get: config.get || emptyFn,
    getMulti: config.getMulti || emptyFn,
    del: config.del || emptyFn,
    delMulti: config.delMulti || emptyFn
  };
  this.keymap = {};
}

function LRUEntry(key) {
  this.key = key;
  this.older = undefined;
  this.newer = undefined;
}

LRUList.prototype._updateStructForPut = function(key, val, done) {
  var entry = new LRUEntry(key); // Create new tail.
  this.keymap[key] = entry;

  if (this.tail) { // Link old tail to new tail.
    this.tail.newer = entry;
    entry.older = this.tail;
  } else { // First entry.
    this.head = entry;
  }

  this.tail = entry; // Assign new tail.

  if (this.size === this.limit) { // List size exceeded. Trim head.
    this.shift(done);
  } else {
    this.size++;
    done(null);
  }
};

LRUList.prototype.putMulti = function(pairs, done) {
  done = done || function putMultiDoneNoOp() {};
  var self = this;

  this.store.setMulti(pairs, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.

    var batch = new Batch();

    Object.keys(pairs).forEach(function batchKey(key) {
      batch.push(function batchPush(taskDone) {
        self._updateStructForPut(key, pairs[key], taskDone);
      });
    });

    batch.end(function batchEnd(err) {
      done(err);
    });
  });
};

/**
 * Append key to the list's tail. Trigger storage of the value.
 *
 * - Duplicate keys are allowed by original design.
 *   May produce "orphaned" entries to which the key map no longer points. Then they
 *   can no longer be read/removed, and can only be pushed out by lack of use.
 *
 * @param {string} key
 * @param {mixed} val
 * @param {function} done
 *   {object} Error instance or null.
 */
LRUList.prototype.put = function(key, val, done) {
  done = done || function putDoneNoOp() {};
  var self = this;
  this.store.set(key, val, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    self._updateStructForPut(key, val, done);
  });
};

/**
 * Remove the key at the list's head (the LRU). Trigger removal of the value.
 *
 * @param {function} done
 *   {object} Error instance or null.
 *   {mixed} Shifted LRUEntry or undefined.
 */
LRUList.prototype.shift = function(done) {
  done = done || function shiftDoneNoOp() {};
  var self = this;

  var entry = this.head;
  if (!entry) { // List already empty.
    done(null);
    return;
  }

  this.store.del(entry.key, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.

    if (self.head.newer) { // 2nd-to-head is now head.
      self.head = self.head.newer;
      self.head.older = undefined;
    } else { // Head was the only entry.
      self.head = undefined;
    }

    // Remove last strong reference to <entry> and remove links from the purged
    // entry being returned.
    entry.newer = entry.older = undefined;

    delete self.keymap[entry.key];
    done(null, entry);
  });
};

LRUList.prototype._updateStructForGet = function(entry) {
  if (entry.newer) { // Key has more-recently-used than it.
    if (entry === this.head) {
      this.head = entry.newer; // 2nd-to-head is now head.
    }
    // Connect adjacent entries to fill the future gap it will leave.
    entry.newer.older = entry.older;
  }
  if (entry.older) { // Key has less-recently-used than it.
    // Connect adjacent entries to fill the future gap it will leave.
    entry.older.newer = entry.newer;
  }

  entry.newer = undefined; // Entry will be newest.

  // Move current tail will 2nd-to-tail positiion.
  entry.older = this.tail;
  if (this.tail) { this.tail.newer = entry; }

  this.tail = entry;
};

LRUList.prototype.getMulti = function(keys, done) {
  done = done || function getMultiDoneNoOp() {};
  var self = this;

  this.store.getMulti(keys, function storeIODone(err, pairs) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    for (var k = 0; k < keys.length; k++) {
      var entry = self.keymap[keys[k]];
      if (entry === undefined) { continue; } // Key miss.
      if (entry === self.tail) { continue; } // Key already MRU.
      self._updateStructForGet(entry);
    }
    done(null, pairs);
  });
};

/**
 * Promote the key to the tail (MRU). Read the value from storage.
 *
 * @param {string|array} key
 * @param {function} done
 *   {object} Error instance or null.
 *   {mixed} Value or undefined.
 */
LRUList.prototype.get = function(key, done) {
  done = done || function getDoneNoOp() {};
  var self = this;

  this.store.get(key, function storeIODone(err, val) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.

    var entry = self.keymap[key];
    if (entry === undefined) { done(null); return; } // Key miss.
    if (entry === self.tail) { done(null, val); return; } // Key already MRU.

    self._updateStructForGet(entry);
    done(null, val);
  });
};

LRUList.prototype._updateStructForRemove = function(key) {
  var entry = this.keymap[key];
  if (!entry) { return; } // Key miss.

  delete this.keymap[entry.key];

  if (entry.newer && entry.older) {
    // Connect adjacent entries to fill the future gap it will leave.
    entry.older.newer = entry.newer;
    entry.newer.older = entry.older;
  } else if (entry.newer) { // Removing head.
    entry.newer.older = undefined;
    this.head = entry.newer;
  } else if (entry.older) { // Removing tail.
    entry.older.newer = undefined;
    this.tail = entry.older;
  } else { // Removing sole key in list.
    this.head = this.tail = undefined;
  }

  this.size--;
};

LRUList.prototype.removeMulti = function(keys, done) {
  done = done || function removeDoneNoOp() {};
  var self = this;

  this.store.delMulti(keys, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    for (var k = 0; k < keys.length; k++) {
      self._updateStructForRemove(keys[k]);
    }
    done(null);
  });
};

/**
 * Remove the key from the list and key map. Trigger removal of the value.
 *
 * @param {string} key
 * @param {function} done
 *   {object} Error instance or null.
 */
LRUList.prototype.remove = function(key, done) {
  done = done || function removeDoneNoOp() {};
  var self = this;

  this.store.del(key, function storeIODone(err) {
    if (err) { done(err); return; } // I/O failed, maintain current list/map.
    self._updateStructForRemove(key);
    done(null);
  });
};

/**
 * Produce a head-to-tail ordered key list.
 *
 * @return {array}
 */
LRUList.prototype.keys = function() {
  var arr = [];
  var entry = this.head;
  while (entry) {
    arr.push(entry.key);
    entry = entry.newer;
  }
  return arr;
};
<|endoftext|>


<commit_before>"use strict";

var _ = require('lodash');
var iterate = require('./lib/iterate');
var extend = require('./lib/extend');

/**
 * Create instance of Schema
 *
 * @constructors
 * @this {Schema}
 * @class Schema
 * @param {string} [name] - register name
 * @return {Schema}
 */
function Schema (name) {
	if (!(this instanceof Schema)) {
		return new Schema(name);
	}

	if (name != null) {
		Schema.register(name, this);
	}

	this.isRequired = true;
}

Schema.prototype = {

	/**
	 * attach this object to schema
	 *
	 * @method
	 * @param {Schema|string} schema - attach destination object.
	 * @param {String} name - field name for attach.
	 * @throws {TypeError} If schema isn't Schema type
	 * @throws {TypeError} If name empty or isn't String type
	 * @throws {Error} If schema already has the same key name
	 * @return {Schema} @this
	 */
	attachTo: function (schema, name) {
		if (_.isString(schema)) {
			schema = Schema.get(schema);
		}

		if (!(schema instanceof Schema)) {
			throw new TypeError('schema node must be instance of Schema');
		}

		if (!name || !_.isString(name)) {
			throw new TypeError('invalid scheme field name must be non-empty string, "' + name + '" ' + Object.prototype.toString.call(name) + ' given');
		}

		this.name = name;
		this.path = (schema.path || []).concat(name);

		schema._addField(this, name);

		schema = null;

		return this;
	},

	/**
	 * @private
	 * @method
	 * @param {Schema} schema - attach destination object.
	 * @param {String} name - field name for attach.
	 * @throws {Error} If schema already has the same key name
	 * @return {Schema} @this
	 */
	_addField: function (schema, name) {
		if (!this.keys) {
			this.keys = [];
		} else if (_.contains(this.keys, name)) {
			throw new Error('duplicate key "' + name + '" at "/' + (schema.path && schema.path.join('/')) + '"');
		}

		this.hasNested = true;

		this.keys.push(name);

		if (!this.fields) {
			this.fields = [];
		}

		this.fields.push(schema);
		schema = null;

		return this;
	},

	/**
	 * set validate options. can be called several times
	 *
	 * @method
	 * @param {Array|Object|String|Function} validation - validation options.
	 * @return {Schema} @this
	 */
	validate: function (validation) {
		if (validation) {
			if (!this.validations) {
				this.validations = [];
			}

			if (!_.isArray(validation)) {
				this.validations.push(validation);
			} else {
				this.validations = this.validations.concat(validation);
			}
		}

		return this;
	},

	/**
	 * set nested fields builder
	 *
	 * @method
	 * @param {Function|Schema} builderOrSchema - builder function, has two functions in arguments [required, options].
	 * @throws {TypeError} If nested is not a function if specified
	 * @return {Schema} @this
	 */
	object: function (builderOrSchema) {
		if (this.hasNested) {
			throw new Error('object already defined');
		}

		if (builderOrSchema instanceof Schema) {
			return this._similar(builderOrSchema);
		}

		if (!_.isFunction(builderOrSchema)) {
			throw new TypeError('nested builder must be function');
		}

		builderOrSchema.call(this, this.required.bind(this), this.optional.bind(this));

		return this;
	},

	/**
	 * set nested fields builder with type array
	 *
	 * @method
	 * @param {Function|Schema|Boolean} builderOrSchema - builder function, has two functions in arguments [required, options].
	 * @throws {TypeError} If nested is not a function if specified
	 * @return {Schema} @this
	 */
	array: function (builderOrSchema) {
		if (_.isFunction(builderOrSchema) || builderOrSchema instanceof Schema) {
			this.object(builderOrSchema);
		}

		this.isArray = builderOrSchema == null ? true : Boolean(builderOrSchema);

		return this;
	},

	/**
	 * add new nested schema by construct
	 *
	 * @method
	 * @param {String} name - field name.
	 * @return {Schema} @this
	 */
	field: function (name) {
		return new Schema().attachTo(this, name);
	},

	/**
	 * clone this schema
	 *
	 * @method
	 * @return {Schema} new object (clone)
	 */
	clone: function () {
		var clone = new Schema()._similar(this);

		if (this.isArray != null) {
			clone.isArray = this.isArray;
		}

		return clone;
	},

	//clean: function () {
	//	var clone = new Schema();
	//
	//	_.each(this.fields, function (fieldSchema) {
	//		fieldSchema.clean().attachTo(clone, fieldSchema.name);
	//	});
	//
	//	if (this.isArray != null) {
	//		clone.isArray = this.isArray;
	//	}
	//
	//	if (this.isRequired != null) {
	//		clone.isRequired = this.isRequired;
	//	}
	//},

	/**
	 * clone schema to this schema object
	 *
	 * @private
	 * @method
	 * @return {Schema} new object (clone)
	 */
	_similar: function (schema) {
		var that = this;

		_.each(schema.fields, function (fieldSchema) {
			fieldSchema.clone().attachTo(that, fieldSchema.name);
		});

		if (schema.validations != null) {
			this.validations = _.cloneDeep(schema.validations);
		}

		if (schema.isRequired != null) {
			this.isRequired = schema.isRequired;
		}

		return this;
	},

	/**
	 * verify value. compare schema with some object.
	 *
	 * @method
	 * @param {*} value - value for check.
	 * @param {Object} [options] - validation options, default is plain object.
	 * @param {Function} [options.validator] - custom validation mapper
	 * @param {Function} done - done-callback.
	 */
	verify: function (value, options, done) {
		if (_.isFunction(options)) {
			done = options;
			options = null;
		}

		options || (options = {});

		if (!_.isFunction(done)) {
			throw new Error('schema verify callback must be function, ' + (typeof done) + ' given');
		}

		var that = this;

		var _done = function (err) {
			if (err instanceof Schema.ValidationResultError) {
				done(null, false, err);
				return;
			}

			if (err) {
				done(err, false, null);
				return;
			}

			done(null, true, null);
		};

		if (value === undefined) {
			if (this.isRequired) {
				_done(new Schema.ValidationResultError('required', null, value, this.path));
				return;
			}

			_done();
			return;
		}

		var validations = this.validations;
		if (!_.isEmpty(validations)) {
			if (options.validator) {
				validations = options.validator(validations);
				if (_.isFunction(validations)) {
					validations = [validations];
				}

				if (!_.isArray(validations)) {
					done(new Error('invalid validation rule type after user-validator mapping'));
					return;
				}
			}

			iterate(validations, function (validation, index, done) {
				validation(value, function (err, isValid, validationError) {
					validationError || (validationError = {});

					if (err) {
						if (err instanceof Schema.ValidationError) {
							err = new Schema.ValidationResultError(err.ruleName, err.ruleParams, value, that.path);
						}
						done(err);
						return;
					}

					if (!isValid) {
						done(new Schema.ValidationResultError(validationError.ruleName, validationError.ruleParams, value, that.path));
						return;
					}

					done(null);
				});
			}, function (err) {
				if (err) {
					done(err);
					return;
				}

				that._validationInner(value, options, _done);
			});
			return;
		}

		that._validationInner(value, options, _done);
	},

	/**
	 * verify inner object
	 *
	 * @private
	 * @method
	 * @param {*} value - value for check
	 * @param {Object} options - validation options
	 * @param {Function} done - done-callback
	 */
	_validationInner: function (value, options, done) {
		var that = this;

		if (Boolean(this.isArray) !== _.isArray(value)) {
			done(new Schema.ValidationResultError('type', this.isArray ? 'array' : 'object', value, this.path));
			return;
		}

		if (this.isArray) {
			iterate(value, function (value, index, done) {
				that._validateFields(value, options, done);
			}, done);

			return;
		}

		that._validateFields(value, options, done);
	},

	/**
	 * verify inner fields
	 *
	 * @private
	 * @method
	 * @param {*} value - value for check
	 * @param {Object} options - validation options
	 * @param {Function} done - done-callback
	 */
	_validateFields: function (value, options, done) {
		if (!this.hasNested) {
			done();
			return;
		}

		if (!_.isObject(value)) {
			done(new Schema.ValidationResultError('type', 'object', value, this.path));
			return;
		}

		var diff = _.difference(_.keys(value), this.keys);
		if (diff.length) {
			done(new Schema.ValidationResultError('available_fields', this.keys, value, this.path));
			return;
		}

		iterate(this.fields, function (fieldSchema, index, done) {
			var fieldValue = value[fieldSchema.name];
			fieldSchema.verify(fieldValue, options, function (err, isValid, validationError) {
				err = err || validationError;
				if (err) {
					done(err);
					return;
				}

				if (isValid) {
					done();
					return;
				}

				done(validationError);
			});
		}, done);
	},

	/**
	 * add new required nested schema by construct
	 *
	 * @method
	 * @param {String} [name] - field name.
	 * @param {*} [validation] - validation options.
	 * @return {Schema}
	 */
	required: function (name, validation) {
		if (!arguments.length) {
			this.isRequired = true;
			return this;
		}

		return this.field(name).validate(validation);
	},

	/**
	 * add new optional nested schema by construct
	 *
	 * @method
	 * @param {String} [name] - field name.
	 * @param {*} [validation] - validation options.
	 * @return {Schema}
	 */
	optional: function (name, validation) {
		if (!arguments.length) {
			this.isRequired = false;
			return this;
		}

		var schema = this.field(name).validate(validation);
		schema.isRequired = false;
		return schema;
	}
};

var register = {};

/**
 * Schema register
 *
 * @static
 * @param {String} name - name for register.
 * @param {Schema} schema - schema for register
 * @throws {TypeError} If invalid name type
 * @throws {TypeError} If schema name type
 * @throws {ReferenceError} If schema was already registered
 * @return {Schema}
 */
Schema.register = function (name, schema) {
	if (!_.isString(name)) {
		throw new TypeError('invalid name type, must be string');
	}

	if (!(schema instanceof Schema)) {
		throw new TypeError('invalid schema type, must be instance of Schema');
	}

	if (register.hasOwnProperty(name)) {
		throw new ReferenceError('schema "' + name + '" was already registered');
	}

	register[name] = schema;
	return schema;
};

/**
 * get registered schema
 *
 * @static
 * @param {String} name - name for register.
 * @throws {TypeError} If invalid name type
 * @throws {ReferenceError} If schema was not registered before
 * @return {Schema}
 */
Schema.get = function (name) {
	if (!_.isString(name)) {
		throw new TypeError('invalid name type, must be string');
	}

	if (!register.hasOwnProperty(name)) {
		throw new ReferenceError('schema "' + name + '" was not registered');
	}

	return register[name];
};

Schema.errorMessageMap = {
	default: function (ruleName, ruleParams, path, value) {
		return 'invalid value ' + ruleName + ' in ' + path;
	}
};

Schema.errorMessage = function (ruleName, ruleParams, path, value) {
	var map = Schema.errorMessageMap[ruleName] || Schema.errorMessageMap.default;
	return map(ruleName, ruleParams, path, value);
};

/**
 * Create instance of Schema.ValidationError. Error object for info about miss value
 *
 * @constructors
 * @extends Error
 * @this {Schema.ValidationError}
 * @class Schema.ValidationError
 * @param {string} ruleName - rule name, that failed
 * @param {*} ruleParams - params of rule, that failed
 * @return {Schema.ValidationError}
 */

Schema.ValidationError = function ValidationError (ruleName, ruleParams) {
	if (!(this instanceof Schema.ValidationError)) {
		return new Schema.ValidationError(ruleName, ruleParams);
	}

	if (!ruleName || !_.isString(ruleName)) {
		throw new TypeError('invalid ruleName, must be non-empty string');
	}

	ruleName || (ruleName = 'unknown');
	this.ruleName = ruleName;
	this.ruleParams = ruleParams;
	this.type = this.name = 'ValidationError';
	Error.call(this);

	return this;
};
extend(Schema.ValidationError, Error);

/**
 * Create instance of Schema.ValidationError. Error object for info about miss value
 *
 * @constructors
 * @extends Schema.ValidationError
 * @this {Schema.ValidationResultError}
 * @class Schema.ValidationResultError
 * @param {string} ruleName - rule name, that failed
 * @param {*} ruleParams - params of rule, that failed
 * @param {*} value - value, that failed
 * @param {Array|null} path -  schema path
 * @return {Schema.ValidationResultError}
 */
Schema.ValidationResultError = function ValidationResultError (ruleName, ruleParams, value, path) {
	if (!(this instanceof Schema.ValidationResultError)) {
		return new Schema.ValidationResultError(ruleName, ruleParams, value, path);
	}

	path       = _.clone(path || []);
	value      = _.cloneDeep(value);
	ruleParams = _.cloneDeep(ruleParams);

	Schema.ValidationError.call(this, ruleName, ruleParams);
	this.message = Schema.errorMessage(ruleName, ruleParams, value, path);
	this.type = this.name = 'ValidationResultError';
	this.value = value;
	this.path = path;

	return this;
};
extend(Schema.ValidationResultError, Schema.ValidationError);

module.exports = Schema;
<commit_msg>rename private method. remove hasNested flag<commit_after>"use strict";

var _ = require('lodash');
var iterate = require('./lib/iterate');
var extend = require('./lib/extend');

/**
 * Create instance of Schema
 *
 * @constructors
 * @this {Schema}
 * @class Schema
 * @param {string} [name] - register name
 * @return {Schema}
 */
function Schema (name) {
	if (!(this instanceof Schema)) {
		return new Schema(name);
	}

	if (name != null) {
		Schema.register(name, this);
	}

	this.isRequired = true;
}

Schema.prototype = {

	/**
	 * attach this object to schema
	 *
	 * @method
	 * @param {Schema|string} schema - attach destination object.
	 * @param {String} name - field name for attach.
	 * @throws {TypeError} If schema isn't Schema type
	 * @throws {TypeError} If name empty or isn't String type
	 * @throws {Error} If schema already has the same key name
	 * @return {Schema} @this
	 */
	attachTo: function (schema, name) {
		if (_.isString(schema)) {
			schema = Schema.get(schema);
		}

		if (!(schema instanceof Schema)) {
			throw new TypeError('schema node must be instance of Schema');
		}

		if (!name || !_.isString(name)) {
			throw new TypeError('invalid scheme field name, must be non-empty string, "' + name + '" ' + Object.prototype.toString.call(name) + ' given');
		}

		return schema._attach(this, name);
	},

	/**
	 * @private
	 * @method
	 * @param {Schema} schema - attach destination object.
	 * @param {String} name - field name for attach.
	 * @throws {Error} If schema already has the same key name
	 * @return {Schema} @this
	 */
	_attach: function (schema, name) {
		schema.name = name;
		schema.path = (this.path || []).concat(name);

		if (!this.keys) {
			this.keys = [];
		} else if (_.contains(this.keys, name)) {
			throw new Error('duplicate key "' + name + '" at "/' + (schema.path && schema.path.join('/')) + '"');
		}

		this.keys.push(name);

		if (!this.fields) {
			this.fields = [];
		}

		this.fields.push(schema);

		return schema;
	},

	/**
	 * set validate options. can be called several times
	 *
	 * @method
	 * @param {Array|Object|String|Function} validation - validation options.
	 * @return {Schema} @this
	 */
	validate: function (validation) {
		if (validation) {
			if (!this.validations) {
				this.validations = [];
			}

			if (!_.isArray(validation)) {
				this.validations.push(validation);
			} else {
				this.validations = this.validations.concat(validation);
			}
		}

		return this;
	},

	/**
	 * set nested fields builder
	 *
	 * @method
	 * @param {Function|Schema} builderOrSchema - builder function, has two functions in arguments [required, options].
	 * @throws {TypeError} If nested is not a function if specified
	 * @return {Schema} @this
	 */
	object: function (builderOrSchema) {
		if (this.fields) {
			throw new Error('object already defined');
		}

		if (builderOrSchema instanceof Schema) {
			return this._similar(builderOrSchema);
		}

		if (!_.isFunction(builderOrSchema)) {
			throw new TypeError('nested builder must be function');
		}

		builderOrSchema.call(this, this.required.bind(this), this.optional.bind(this));

		return this;
	},

	/**
	 * set nested fields builder with type array
	 *
	 * @method
	 * @param {Function|Schema|Boolean} builderOrSchema - builder function, has two functions in arguments [required, options].
	 * @throws {TypeError} If nested is not a function if specified
	 * @return {Schema} @this
	 */
	array: function (builderOrSchema) {
		if (_.isFunction(builderOrSchema) || builderOrSchema instanceof Schema) {
			this.object(builderOrSchema);
		}

		this.isArray = builderOrSchema == null ? true : Boolean(builderOrSchema);

		return this;
	},

	/**
	 * add new nested schema by construct
	 *
	 * @method
	 * @param {String} name - field name.
	 * @return {Schema} @this
	 */
	field: function (name) {
		return new Schema().attachTo(this, name);
	},

	/**
	 * clone this schema
	 *
	 * @method
	 * @return {Schema} new object (clone)
	 */
	clone: function () {
		var clone = new Schema()._similar(this);

		if (this.isArray != null) {
			clone.isArray = this.isArray;
		}

		return clone;
	},

	/**
	 * clone schema to this schema object
	 *
	 * @private
	 * @method
	 * @return {Schema} new object (clone)
	 */
	_similar: function (schema) {
		var that = this;

		_.each(schema.fields, function (fieldSchema) {
			fieldSchema.clone().attachTo(that, fieldSchema.name);
		});

		if (schema.validations != null) {
			this.validations = _.cloneDeep(schema.validations);
		}

		if (schema.isRequired != null) {
			this.isRequired = schema.isRequired;
		}

		return this;
	},

	/**
	 * verify value. compare schema with some object.
	 *
	 * @method
	 * @param {*} value - value for check.
	 * @param {Object} [options] - validation options, default is plain object.
	 * @param {Function} [options.validator] - custom validation mapper
	 * @param {Function} done - done-callback.
	 */
	verify: function (value, options, done) {
		if (_.isFunction(options)) {
			done = options;
			options = null;
		}

		options || (options = {});

		if (!_.isFunction(done)) {
			throw new Error('schema verify callback must be function, ' + (typeof done) + ' given');
		}

		var that = this;

		var _done = function (err) {
			if (err instanceof Schema.ValidationResultError) {
				done(null, false, err);
				return;
			}

			if (err) {
				done(err, false, null);
				return;
			}

			done(null, true, null);
		};

		if (value === undefined) {
			if (this.isRequired) {
				_done(new Schema.ValidationResultError('required', null, value, this.path));
				return;
			}

			_done();
			return;
		}

		var validations = this.validations;
		if (!_.isEmpty(validations)) {
			if (options.validator) {
				validations = options.validator(validations);
				if (_.isFunction(validations)) {
					validations = [validations];
				}

				if (!_.isArray(validations)) {
					done(new Error('invalid validation rule type after user-validator mapping'));
					return;
				}
			}

			iterate(validations, function (validation, index, done) {
				validation(value, function (err, isValid, validationError) {
					validationError || (validationError = {});

					if (err) {
						if (err instanceof Schema.ValidationError) {
							err = new Schema.ValidationResultError(err.ruleName, err.ruleParams, value, that.path);
						}
						done(err);
						return;
					}

					if (!isValid) {
						done(new Schema.ValidationResultError(validationError.ruleName, validationError.ruleParams, value, that.path));
						return;
					}

					done(null);
				});
			}, function (err) {
				if (err) {
					done(err);
					return;
				}

				that._validationInner(value, options, _done);
			});
			return;
		}

		that._validationInner(value, options, _done);
	},

	/**
	 * verify inner object
	 *
	 * @private
	 * @method
	 * @param {*} value - value for check
	 * @param {Object} options - validation options
	 * @param {Function} done - done-callback
	 */
	_validationInner: function (value, options, done) {
		var that = this;

		if (Boolean(this.isArray) !== _.isArray(value)) {
			done(new Schema.ValidationResultError('type', this.isArray ? 'array' : 'object', value, this.path));
			return;
		}

		if (this.isArray) {
			iterate(value, function (value, index, done) {
				that._validateFields(value, options, done);
			}, done);

			return;
		}

		that._validateFields(value, options, done);
	},

	/**
	 * verify inner fields
	 *
	 * @private
	 * @method
	 * @param {*} value - value for check
	 * @param {Object} options - validation options
	 * @param {Function} done - done-callback
	 */
	_validateFields: function (value, options, done) {
		if (!this.fields) {
			done();
			return;
		}

		if (!_.isObject(value)) {
			done(new Schema.ValidationResultError('type', 'object', value, this.path));
			return;
		}

		var diff = _.difference(_.keys(value), this.keys);
		if (diff.length) {
			done(new Schema.ValidationResultError('available_fields', this.keys, value, this.path));
			return;
		}

		iterate(this.fields, function (fieldSchema, index, done) {
			var fieldValue = value[fieldSchema.name];
			fieldSchema.verify(fieldValue, options, function (err, isValid, validationError) {
				err = err || validationError;
				if (err) {
					done(err);
					return;
				}

				if (isValid) {
					done();
					return;
				}

				done(validationError);
			});
		}, done);
	},

	/**
	 * add new required nested schema by construct
	 *
	 * @method
	 * @param {String} [name] - field name.
	 * @param {*} [validation] - validation options.
	 * @return {Schema}
	 */
	required: function (name, validation) {
		if (!arguments.length) {
			this.isRequired = true;
			return this;
		}

		return this.field(name).validate(validation);
	},

	/**
	 * add new optional nested schema by construct
	 *
	 * @method
	 * @param {String} [name] - field name.
	 * @param {*} [validation] - validation options.
	 * @return {Schema}
	 */
	optional: function (name, validation) {
		if (!arguments.length) {
			this.isRequired = false;
			return this;
		}

		var schema = this.field(name).validate(validation);
		schema.isRequired = false;
		return schema;
	}
};

var register = {};

/**
 * Schema register
 *
 * @static
 * @param {String} name - name for register.
 * @param {Schema} schema - schema for register
 * @throws {TypeError} If invalid name type
 * @throws {TypeError} If schema name type
 * @throws {ReferenceError} If schema was already registered
 * @return {Schema}
 */
Schema.register = function (name, schema) {
	if (!_.isString(name)) {
		throw new TypeError('invalid name type, must be string');
	}

	if (!(schema instanceof Schema)) {
		throw new TypeError('invalid schema type, must be instance of Schema');
	}

	if (register.hasOwnProperty(name)) {
		throw new ReferenceError('schema "' + name + '" was already registered');
	}

	register[name] = schema;
	return schema;
};

/**
 * get registered schema
 *
 * @static
 * @param {String} name - name for register.
 * @throws {TypeError} If invalid name type
 * @throws {ReferenceError} If schema was not registered before
 * @return {Schema}
 */
Schema.get = function (name) {
	if (!_.isString(name)) {
		throw new TypeError('invalid name type, must be string');
	}

	if (!register.hasOwnProperty(name)) {
		throw new ReferenceError('schema "' + name + '" was not registered');
	}

	return register[name];
};

Schema.errorMessageMap = {
	default: function (ruleName, ruleParams, path, value) {
		return 'invalid value ' + ruleName + ' in ' + path;
	}
};

Schema.errorMessage = function (ruleName, ruleParams, path, value) {
	var map = Schema.errorMessageMap[ruleName] || Schema.errorMessageMap.default;
	return map(ruleName, ruleParams, path, value);
};

/**
 * Create instance of Schema.ValidationError. Error object for info about miss value
 *
 * @constructors
 * @extends Error
 * @this {Schema.ValidationError}
 * @class Schema.ValidationError
 * @param {string} ruleName - rule name, that failed
 * @param {*} ruleParams - params of rule, that failed
 * @return {Schema.ValidationError}
 */

Schema.ValidationError = function ValidationError (ruleName, ruleParams) {
	if (!(this instanceof Schema.ValidationError)) {
		return new Schema.ValidationError(ruleName, ruleParams);
	}

	if (!ruleName || !_.isString(ruleName)) {
		throw new TypeError('invalid ruleName, must be non-empty string');
	}

	ruleName || (ruleName = 'unknown');
	this.ruleName = ruleName;
	this.ruleParams = ruleParams;
	this.type = this.name = 'ValidationError';
	Error.call(this);

	return this;
};
extend(Schema.ValidationError, Error);

/**
 * Create instance of Schema.ValidationError. Error object for info about miss value
 *
 * @constructors
 * @extends Schema.ValidationError
 * @this {Schema.ValidationResultError}
 * @class Schema.ValidationResultError
 * @param {string} ruleName - rule name, that failed
 * @param {*} ruleParams - params of rule, that failed
 * @param {*} value - value, that failed
 * @param {Array|null} path -  schema path
 * @return {Schema.ValidationResultError}
 */
Schema.ValidationResultError = function ValidationResultError (ruleName, ruleParams, value, path) {
	if (!(this instanceof Schema.ValidationResultError)) {
		return new Schema.ValidationResultError(ruleName, ruleParams, value, path);
	}

	path       = _.clone(path || []);
	value      = _.cloneDeep(value);
	ruleParams = _.cloneDeep(ruleParams);

	Schema.ValidationError.call(this, ruleName, ruleParams);
	this.message = Schema.errorMessage(ruleName, ruleParams, value, path);
	this.type = this.name = 'ValidationResultError';
	this.value = value;
	this.path = path;

	return this;
};
extend(Schema.ValidationResultError, Schema.ValidationError);

module.exports = Schema;
<|endoftext|>


<commit_before>'use strict';

const util = require('util');
const leftPad = require('left-pad');

/**
 * Auto combat module
 */
module.exports = (srcPath) => {
  const Broadcast = require(srcPath + 'Broadcast');
  const LevelUtil = require(srcPath + 'LevelUtil');
  const Damage = require(srcPath + 'Damage');
  const Player = require(srcPath + 'Player');
  const RandomUtil = require(srcPath + 'RandomUtil');

  return  {
    listeners: {
      updateTick: state => function () {
        // Check to see if the player has died since the last combat tick. If
        // we only did the check right when the player was damaged then you
        // could potentially wind up in a situation where the player performed
        // a mid-round attack that killed the target, then the next round the
        // target kills the player. So let's not let that happen.
        if (this.getAttribute('health') <= 0) {
          return handleDeath(state, this);
        }

        if (!this.isInCombat()) {
          return startRegeneration(state, this);
        }

        // TODO: For now player/enemy speed is a fixed 2.5 seconds, base it off weapon speed later
        this.combatData.speed = 1.5;
        const targetSpeed = 2;

        let hadActions = false;
        for (const target of this.combatants) {
          target.combatData.speed = target.combatData.speed || targetSpeed;

          // player actions
          if (target.getAttribute('health') <= 0) {

            Broadcast.sayAt(this, `<bold>${target.name} is <red>Dead</red>!</bold>`);

            handleDeath(state, target, this);

            // TODO: For now respawn happens here, this is shitty
            const newNpc = state.MobFactory.clone(target);
            newNpc.hydrate(state);
            if (newNpc.room) {
              newNpc.room.removeNpc(newNpc);
            }
            target.room.addNpc(newNpc);
            target.emit('spawn');
            target.area.removeNpc(target);
            continue;
          }

          if (this.combatData.lag <= 0) {
            hadActions = true;
            makeAttack(this, target);
          } else {
            const elapsed = Date.now() - this.combatData.roundStarted;
            this.combatData.lag -= elapsed;
          }

          this.combatData.roundStarted = Date.now();

          // target actions
          if (target.combatData.lag <= 0) {
            hadActions = true;
            makeAttack(target, this);

            //TODO: Better way or timing of checking for death?
            if (this.getAttribute('health') <= 0) {
              this.combatData.killedBy = target;
              break;
            }

          } else {
            const elapsed = Date.now() - target.combatData.roundStarted;
            target.combatData.lag -= elapsed;
          }
          target.combatData.roundStarted = Date.now();
        }

        if (!this.isInCombat()) {
          // reset combat data to remove any lag
          this.combatData = {};
          this.removePrompt('combat');
        }

        // Show combat prompt and health bars.
        if (hadActions) {
          if (this.isInCombat()) {
            this.addPrompt('combat', () => {
              if (!this.isInCombat()) {
                return '';
              }

              // Set up some constants for formatting the health bars
              const playerName = "You";
              const targetNameLengths = [...this.combatants].map(t => t.name.length);
              const nameWidth = Math.max(playerName.length, ...targetNameLengths);
              const progWidth = 60 - nameWidth;

              // Set up helper functions for health-bar-building.
              const getHealthPercentage = entity => Math.floor((entity.getAttribute('health') / entity.getMaxAttribute('health')) * 100);
              const formatProgressBar = (name, progress, entity) =>
                `<bold>${leftPad(name, nameWidth)}</bold>: ${progress} <bold>${entity.getAttribute('health')}/${entity.getMaxAttribute('health')}</bold>`

              // Build player health bar.
              let currentPerc = getHealthPercentage(this);
              let progress = Broadcast.progress(progWidth, currentPerc, "green");
              let buf = formatProgressBar(playerName, progress, this);

              // Build and add target health bars.
              for (const target of this.combatants) {
                let currentPerc = Math.floor((target.getAttribute('health') / target.getMaxAttribute('health')) * 100);
                let progress = Broadcast.progress(progWidth, currentPerc, "red");
                buf += `\r\n${formatProgressBar(target.name, progress, target)}`;
              }

              return buf;
            });
          }

          Broadcast.sayAt(this, '');
          Broadcast.prompt(this);
        }

      },

      /**
       * When the player hits a target
       * @param {Damage} damage
       * @param {Character} target
       */
      hit: state => function (damage, target) {
        if (damage.hidden) {
          return;
        }

        let buf = '';
        if (damage.source) {
          buf = `Your <bold>${damage.source.name}</bold> hit`;
        } else {
          buf = "You hit";
        }

        buf += ` <bold>${target.name}</bold> for <bold>${damage.finalAmount}</bold> damage.`;
        Broadcast.sayAt(this, buf);
      },

      /**
       * @param {Heal} heal
       * @param {Character} target
       */
      heal: state => function (heal, target) {
        if (heal.hidden) {
          return;
        }

        let buf = '';
        if (heal.source) {
          buf = `Your <bold>${heal.source.name}</bold> healed`;
        } else {
          buf = "You heal";
        }

        buf += '<bold> ' + (target === this ? 'You' : `${target.name}`);
        buf += ` <bold><green>${heal.finalAmount}</green></bold> ${heal.attribute}`;
        Broadcast.sayAt(this, buf);
      },

      damaged: state => function (damage) {
        if (damage.hidden || damage.attribute !== 'health') {
          return;
        }

        let buf = '';
        if (damage.attacker) {
          buf = `<bold>${damage.attacker.name}</bold>`;
        }

        if (damage.source) {
          buf += (damage.attacker ? "'s " : "") + `<bold>${damage.source.name}</bold>`;
        } else if (!damage.attacker) {
          buf += "Something";
        }

        buf += ` hit <bold>You</bold> for <bold><red>${damage.finalAmount}</red></bold> damage`;
        Broadcast.sayAt(this, buf);
      },

      /**
       * Player was killed
       * @param {Character} killer
       */
      killed: state => function (killer) {
        Broadcast.sayAt(this, "Whoops, that sucked!");
        Broadcast.prompt(this);
      },

      /**
       * Player killed a target
       * @param {Character} target
       */
      deathblow: state => function (target) {
        this.emit('experience', LevelUtil.mobExp(target.level));
        Broadcast.prompt(this);
      }
    }
  };

  function makeAttack(attacker, defender) {
    const amount = RandomUtil.inRange(5, 20);

    const damage = new Damage({
      attribute: "health",
      amount,
      attacker
    });
    damage.commit(defender);

    attacker.combatData.lag = attacker.combatData.speed * 1000;
  }

  function handleDeath(state, deadEntity, killer) {
    deadEntity.combatants.forEach(combatant => {
      deadEntity.removeCombatant(combatant);
      combatant.removeCombatant(deadEntity);
    }, deadEntity);

    if (deadEntity instanceof Player) {
      deadEntity.removePrompt('combat');
    }

    const target = killer || deadEntity.combatData.killedBy;

    if (target) {
      target.emit('deathblow', deadEntity);
      if (!target.isInCombat()) {
        startRegeneration(state, target);
      }
    }

    const deathMessage = target ?
      `<bold><red>${target.name} killed you!</red></bold>` :
      `<bold><red>You died!</red></bold>`;

    if (Broadcast.isBroadcastable(deadEntity)) {
      Broadcast.sayAt(deadEntity, deathMessage);
    }

    deadEntity.emit('killed', target || deadEntity);
  }

  // Make characters regenerate health while out of combat
  function startRegeneration(state, entity) {
    if (entity.getAttribute('health') < entity.getMaxAttribute('health')) {
      let regenEffect = state.EffectFactory.create('regen', entity, { hidden: true }, { magnitude: 15 });
      if (entity.addEffect(regenEffect)) {
        regenEffect.activate();
      }
    }
  }

};

<commit_msg>Set health to max upon death, insta-respawn (#122)<commit_after>'use strict';

const util = require('util');
const leftPad = require('left-pad');

/**
 * Auto combat module
 */
module.exports = (srcPath) => {
  const Broadcast = require(srcPath + 'Broadcast');
  const LevelUtil = require(srcPath + 'LevelUtil');
  const Damage = require(srcPath + 'Damage');
  const Player = require(srcPath + 'Player');
  const RandomUtil = require(srcPath + 'RandomUtil');

  return  {
    listeners: {
      updateTick: state => function () {
        // Check to see if the player has died since the last combat tick. If
        // we only did the check right when the player was damaged then you
        // could potentially wind up in a situation where the player performed
        // a mid-round attack that killed the target, then the next round the
        // target kills the player. So let's not let that happen.
        if (this.getAttribute('health') <= 0) {
          return handleDeath(state, this);
        }

        if (!this.isInCombat()) {
          return startRegeneration(state, this);
        }

        // TODO: For now player/enemy speed is a fixed 2.5 seconds, base it off weapon speed later
        this.combatData.speed = 1.5;
        const targetSpeed = 2;

        let hadActions = false;
        for (const target of this.combatants) {
          target.combatData.speed = target.combatData.speed || targetSpeed;

          // player actions
          if (target.getAttribute('health') <= 0) {

            Broadcast.sayAt(this, `<bold>${target.name} is <red>Dead</red>!</bold>`);

            handleDeath(state, target, this);

            // TODO: For now respawn happens here, this is shitty
            const newNpc = state.MobFactory.clone(target);
            newNpc.hydrate(state);
            if (newNpc.room) {
              newNpc.room.removeNpc(newNpc);
            }
            target.room.addNpc(newNpc);
            target.emit('spawn');
            target.area.removeNpc(target);
            continue;
          }

          if (this.combatData.lag <= 0) {
            hadActions = true;
            makeAttack(this, target);
          } else {
            const elapsed = Date.now() - this.combatData.roundStarted;
            this.combatData.lag -= elapsed;
          }

          this.combatData.roundStarted = Date.now();

          // target actions
          if (target.combatData.lag <= 0) {
            hadActions = true;
            makeAttack(target, this);

            //TODO: Better way or timing of checking for death?
            if (this.getAttribute('health') <= 0) {
              this.combatData.killedBy = target;
              break;
            }

          } else {
            const elapsed = Date.now() - target.combatData.roundStarted;
            target.combatData.lag -= elapsed;
          }
          target.combatData.roundStarted = Date.now();
        }

        if (!this.isInCombat()) {
          // reset combat data to remove any lag
          this.combatData = {};
          this.removePrompt('combat');
        }

        // Show combat prompt and health bars.
        if (hadActions) {
          if (this.isInCombat()) {
            this.addPrompt('combat', () => {
              if (!this.isInCombat()) {
                return '';
              }

              // Set up some constants for formatting the health bars
              const playerName = "You";
              const targetNameLengths = [...this.combatants].map(t => t.name.length);
              const nameWidth = Math.max(playerName.length, ...targetNameLengths);
              const progWidth = 60 - nameWidth;

              // Set up helper functions for health-bar-building.
              const getHealthPercentage = entity => Math.floor((entity.getAttribute('health') / entity.getMaxAttribute('health')) * 100);
              const formatProgressBar = (name, progress, entity) =>
                `<bold>${leftPad(name, nameWidth)}</bold>: ${progress} <bold>${entity.getAttribute('health')}/${entity.getMaxAttribute('health')}</bold>`

              // Build player health bar.
              let currentPerc = getHealthPercentage(this);
              let progress = Broadcast.progress(progWidth, currentPerc, "green");
              let buf = formatProgressBar(playerName, progress, this);

              // Build and add target health bars.
              for (const target of this.combatants) {
                let currentPerc = Math.floor((target.getAttribute('health') / target.getMaxAttribute('health')) * 100);
                let progress = Broadcast.progress(progWidth, currentPerc, "red");
                buf += `\r\n${formatProgressBar(target.name, progress, target)}`;
              }

              return buf;
            });
          }

          Broadcast.sayAt(this, '');
          Broadcast.prompt(this);
        }

      },

      /**
       * When the player hits a target
       * @param {Damage} damage
       * @param {Character} target
       */
      hit: state => function (damage, target) {
        if (damage.hidden) {
          return;
        }

        let buf = '';
        if (damage.source) {
          buf = `Your <bold>${damage.source.name}</bold> hit`;
        } else {
          buf = "You hit";
        }

        buf += ` <bold>${target.name}</bold> for <bold>${damage.finalAmount}</bold> damage.`;
        Broadcast.sayAt(this, buf);
      },

      /**
       * @param {Heal} heal
       * @param {Character} target
       */
      heal: state => function (heal, target) {
        if (heal.hidden) {
          return;
        }

        let buf = '';
        if (heal.source) {
          buf = `Your <bold>${heal.source.name}</bold> healed`;
        } else {
          buf = "You heal";
        }

        buf += '<bold> ' + (target === this ? 'You' : `${target.name}`);
        buf += ` <bold><green>${heal.finalAmount}</green></bold> ${heal.attribute}`;
        Broadcast.sayAt(this, buf);
      },

      damaged: state => function (damage) {
        if (damage.hidden || damage.attribute !== 'health') {
          return;
        }

        let buf = '';
        if (damage.attacker) {
          buf = `<bold>${damage.attacker.name}</bold>`;
        }

        if (damage.source) {
          buf += (damage.attacker ? "'s " : "") + `<bold>${damage.source.name}</bold>`;
        } else if (!damage.attacker) {
          buf += "Something";
        }

        buf += ` hit <bold>You</bold> for <bold><red>${damage.finalAmount}</red></bold> damage`;
        Broadcast.sayAt(this, buf);
      },

      /**
       * Player was killed
       * @param {Character} killer
       */
      killed: state => function (killer) {
        this.setAttributeToMax('health');
        Broadcast.sayAt(this, "Whoops, that sucked!");
        Broadcast.prompt(this);
      },

      /**
       * Player killed a target
       * @param {Character} target
       */
      deathblow: state => function (target) {
        this.emit('experience', LevelUtil.mobExp(target.level));
        Broadcast.prompt(this);
      }
    }
  };

  function makeAttack(attacker, defender) {
    const amount = RandomUtil.inRange(5, 20);

    const damage = new Damage({
      attribute: "health",
      amount,
      attacker
    });
    damage.commit(defender);

    attacker.combatData.lag = attacker.combatData.speed * 1000;
  }

  function handleDeath(state, deadEntity, killer) {
    deadEntity.combatants.forEach(combatant => {
      deadEntity.removeCombatant(combatant);
      combatant.removeCombatant(deadEntity);
    }, deadEntity);

    if (deadEntity instanceof Player) {
      deadEntity.removePrompt('combat');
    }

    const target = killer || deadEntity.combatData.killedBy;

    if (target) {
      target.emit('deathblow', deadEntity);
      if (!target.isInCombat()) {
        startRegeneration(state, target);
      }
    }

    const deathMessage = target ?
      `<bold><red>${target.name} killed you!</red></bold>` :
      `<bold><red>You died!</red></bold>`;

    if (Broadcast.isBroadcastable(deadEntity)) {
      Broadcast.sayAt(deadEntity, deathMessage);
    }

    deadEntity.emit('killed', target || deadEntity);
  }

  // Make characters regenerate health while out of combat
  function startRegeneration(state, entity) {
    if (entity.getAttribute('health') < entity.getMaxAttribute('health')) {
      let regenEffect = state.EffectFactory.create('regen', entity, { hidden: true }, { magnitude: 15 });
      if (entity.addEffect(regenEffect)) {
        regenEffect.activate();
      }
    }
  }

};

<|endoftext|>


<commit_before>/*global require, module*/
var winston = require('winston'),

    config  = {
      levels: {
        panic: 0,
        alert: 1,
        crit: 2,
        error: 3,
        warn: 4,
        notice: 5,
        info: 6,
        log: 6,
        debug: 7
      },

      colors: {
        panic: 'red',
        alert: 'red',
        crit: 'red',
        error: 'red',
        warn: 'orange',
        notice: 'yellow',
        info: 'magenta',
        log: 'cyan',
        debug: 'blue'
      }
    },

    logger  = new (winston.Logger)({
      levels: config.levels,
      colors: config.colors
    }),

    currentLevelName = null,

    init = function (level, colorize, timestamp) {
      level = level || 'info'; // default level is info.

      if(typeof colorize === 'undefined') {
        colorize = true;  // we colorize log output by default.
      }
      if(typeof timestamp === 'undefined') {
        timestamp = false; // we don't set a timestamp by default.
      }
      //If we have a currentLevelName that means that we are re-initializing.
      if(currentLevelName) {
        logger.remove(winston.transports.Console);
      }

      logger.add(winston.transports.Console, {
        level: level,
        colorize: colorize,
        timestamp: timestamp
      });

      logger.log("LOGGER - Setting `log level` to: " + level + "(" + config.levels[level] + ")");
      currentLevelName = level;
    }
;

init();  //initialize on load.

logger.setLevel = function (level) {
  if(level !== currentLevelName) {
    init(level);
  }
};

logger.cli();

// Make level(int) to string object.
level_to_name = (function (res) {
  var curLevel, curLevelName;

  for(curLevelName in config.levels) {
    curLevel = config.levels[curLevelName];

    if(!res[curLevel]) {
      res[curLevel] = curLevelName;
      res.length += 1;
    }
  }
}({ length: 0}));

// Allow the user to toggle through the log levels by sending a SIGUSR2 signal
// to the process.
process.on(PROCESS_SIGNAL, function () {
  var current_level = config.levels[currentLevelName],
      next_level    = current_level + 1,
      next_level_name
  ;
  if (next_level >= level_to_name.length) {
    next_level = 0;
  }
  next_level_name = level_to_name[next_level];

  logger.setLevel(next_level_name);
});

module.exports = logger;<commit_msg>Get module in working order.<commit_after>/*global require, module*/
var winston = require('winston'),

    config  = {
      levels: {
        panic: 0,
        alert: 1,
        crit: 2,
        error: 3,
        warn: 4,
        notice: 5,
        info: 6,
        log: 6,
        debug: 7
      },

      colors: {
        panic: 'red',
        alert: 'red',
        crit: 'red',
        error: 'red',
        warn: 'orange',
        notice: 'yellow',
        info: 'magenta',
        log: 'cyan',
        debug: 'blue'
      }
    },

    logger  = new (winston.Logger)({
      levels: config.levels,
      colors: config.colors
    }),

    currentLevelName = null,

    init = function (level, colorize, timestamp) {
      level = level || 'info'; // default level is info.

      if(typeof colorize === 'undefined') {
        colorize = true;  // we colorize log output by default.
      }
      if(typeof timestamp === 'undefined') {
        timestamp = false; // we don't set a timestamp by default.
      }
      //If we have a currentLevelName that means that we are re-initializing.
      if(currentLevelName) {
        logger.remove(winston.transports.Console);
      }

      logger.add(winston.transports.Console, {
        level: level,
        colorize: colorize,
        timestamp: timestamp
      });

      setTimeout(logger.info.bind(logger), 0,
        "LOGGER - Setting `log level` to: " + level + "(" + config.levels[level] + ")"
      );

      currentLevelName = level;
    }
;

init();  //initialize on load.

logger.setLevel = function (level) {
  if(level !== currentLevelName) {
    init(level);
  }
};

logger.cli();

// Make level(int) to string object.
level_to_name = (function (res) {
  var curLevel, curLevelName;

  for(curLevelName in config.levels) {
    curLevel = config.levels[curLevelName];

    if(!res[curLevel]) {
      res[curLevel] = curLevelName;
      res.length += 1;
    }
  }
}({ length: 0}));

// Allow the user to toggle through the log levels by sending a SIGUSR2 signal
// to the process.
process.on('SIGUSR2', function () {
  var current_level = config.levels[currentLevelName],
      next_level    = current_level + 1,
      next_level_name
  ;
  if (next_level >= level_to_name.length) {
    next_level = 0;
  }
  next_level_name = level_to_name[next_level];

  logger.setLevel(next_level_name);
});

module.exports = logger;<|endoftext|>


<commit_before>// @flow

import React, { Component } from 'react';
import {
  View,
  StyleSheet,
  Dimensions,
  TouchableNativeFeedback,
  TouchableOpacity,
  Text,
  Animated,
  Platform
} from 'react-native';

type Props = {
  barColor: string,
  activeTextColor: string,
  indicatorColor: string,
  items: string[],
  selectedIndex: number,
  onChange: (number) => void
}

type DefaultProps = {
  barColor: string,
  selectedIndex: number,
  activeTextColor: string,
  indicatorColor: string
}

type State = {
  tabWidth: number,
  indicatorPosition: Animated.Value
}

class MaterialTabs extends Component {
  props: Props;
  static defaultProps: DefaultProps;
  state: State;

  state = {
    tabWidth: 0,
    indicatorPosition: new Animated.Value(0)
  }

  componentDidUpdate() {
    this._selectTab();
  }

  _findDimensions(event) {
    let {width} = event.nativeEvent.layout;

    this.setState({
      tabWidth: width/this.props.items.length
    })
  }

  _selectTab() {
    let {selectedIndex} = this.props;

    Animated.spring(
      this.state.indicatorPosition,
      {
        toValue: selectedIndex === 0 ? 0 : selectedIndex * this.state.tabWidth,
        tension: 100,
        friction: 10
      }
    ).start();
  }

  render() {
    const {tabWidth} = this.state;

    const styles = StyleSheet.create({
      tabBar: {
        flex: 1,
        backgroundColor: this.props.barColor,
        maxHeight: 48
      },
      tabTrack: {
        minHeight: 46,
        flexDirection: 'row'
      },
      tab: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center'
      },
      label: {
        color: 'rgba(255, 255, 255, 0.7)',
        fontWeight: '500',
        fontSize: 14
      },
      labelSelected: {
        color: this.props.activeTextColor
      },
      indicator: {
        backgroundColor: this.props.indicatorColor,
        width: tabWidth,
        height: 2,
        position: 'absolute',
        bottom: 0
      }
    })

    const tab = (item, idx) => {
      if(Platform.OS === 'android') {
        return (
          <TouchableNativeFeedback key={idx} onPress={() => this.props.onChange(idx)}>
            <View style={styles.tab}>
              <Text style={[styles.label, this.props.selectedIndex === idx ? styles.labelSelected : '']}>{item.toUpperCase()}</Text>
            </View>
          </TouchableNativeFeedback>
        )
      }

      return (
        <TouchableOpacity key={idx} onPress={() => this.props.onChange(idx)}  style={styles.tab}>
          <Text style={[styles.label, this.props.selectedIndex === idx ? styles.labelSelected : '']}>{item.toUpperCase()}</Text>
        </TouchableOpacity>
      )
    }

    return (
      <View style={styles.tabBar} onLayout={this._findDimensions.bind(this)}>
        <View style={styles.tabTrack}>
          {this.props.items.map((item, idx) => (
            tab(item, idx)
          ))}
        </View>

        <Animated.View style={[styles.indicator, {left: this.state.indicatorPosition}]}/>
      </View>
    )
  }
}

MaterialTabs.defaultProps = {
  barColor: '#13897b',
  indicatorColor: '#fff',
  activeTextColor: '#fff',
  selectedIndex: 0
}

export default MaterialTabs;<commit_msg>Fixed bug where tab bar wasn't displaying<commit_after>// @flow

import React, { Component } from 'react';
import {
  View,
  StyleSheet,
  Dimensions,
  TouchableNativeFeedback,
  TouchableOpacity,
  Text,
  Animated,
  Platform
} from 'react-native';

type Props = {
  barColor: string,
  activeTextColor: string,
  indicatorColor: string,
  items: string[],
  selectedIndex: number,
  onChange: (number) => void
}

type DefaultProps = {
  barColor: string,
  selectedIndex: number,
  activeTextColor: string,
  indicatorColor: string
}

type State = {
  tabWidth: number,
  indicatorPosition: Animated.Value
}

class MaterialTabs extends Component {
  props: Props;
  static defaultProps: DefaultProps;
  state: State;

  state = {
    tabWidth: 0,
    indicatorPosition: new Animated.Value(0)
  }

  componentDidUpdate() {
    this._selectTab();
  }

  _findDimensions(event) {
    let {width} = event.nativeEvent.layout;

    this.setState({
      tabWidth: width/this.props.items.length
    })
  }

  _selectTab() {
    let {selectedIndex} = this.props;

    Animated.spring(
      this.state.indicatorPosition,
      {
        toValue: selectedIndex === 0 ? 0 : selectedIndex * this.state.tabWidth,
        tension: 100,
        friction: 10
      }
    ).start();
  }

  render() {
    const {tabWidth} = this.state;

    const styles = StyleSheet.create({
      tabBar: {
        backgroundColor: this.props.barColor,
        maxHeight: 48
      },
      tabTrack: {
        minHeight: 46,
        flexDirection: 'row'
      },
      tab: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center'
      },
      label: {
        color: 'rgba(255, 255, 255, 0.7)',
        fontWeight: '500',
        fontSize: 14
      },
      labelSelected: {
        color: this.props.activeTextColor
      },
      indicator: {
        backgroundColor: this.props.indicatorColor,
        width: tabWidth,
        height: 2,
        position: 'absolute',
        bottom: 0
      }
    })

    const tab = (item, idx) => {
      if(Platform.OS === 'android') {
        return (
          <TouchableNativeFeedback key={idx} onPress={() => this.props.onChange(idx)}>
            <View style={styles.tab}>
              <Text style={[styles.label, this.props.selectedIndex === idx ? styles.labelSelected : '']}>{item.toUpperCase()}</Text>
            </View>
          </TouchableNativeFeedback>
        )
      }

      return (
        <TouchableOpacity key={idx} onPress={() => this.props.onChange(idx)}  style={styles.tab}>
          <Text style={[styles.label, this.props.selectedIndex === idx ? styles.labelSelected : '']}>{item.toUpperCase()}</Text>
        </TouchableOpacity>
      )
    }

    return (
      <View style={styles.tabBar} onLayout={this._findDimensions.bind(this)}>
        <View style={styles.tabTrack}>
          {this.props.items.map((item, idx) => (
            tab(item, idx)
          ))}
        </View>

        <Animated.View style={[styles.indicator, {left: this.state.indicatorPosition}]}/>
      </View>
    )
  }
}

MaterialTabs.defaultProps = {
  barColor: '#13897b',
  indicatorColor: '#fff',
  activeTextColor: '#fff',
  selectedIndex: 0
}

export default MaterialTabs;<|endoftext|>


<commit_before>var restify = require('restify');
var request = require('superagent');
var jsdom = require('jsdom');
var eachSeries = require('async/eachSeries');

var server = restify.createServer({
  name: 'sempre geo lookup',
  version: '1.0.0'
});

server.use(restify.queryParser());
server.use(restify.bodyParser());

server.get('/', function (req, res, next) {
  console.log('GET /');
  var ip = '12.201.135.66';
  request
    .post('https://www.iplocation.net/')
    .send('query=' + ip)
    .send('submit=IP Lookup')
    .end(
      function (err, lookupResponseHTML) {
        if (err) {
          return next(err);
        }

        parseIpPage(lookupResponseHTML.text, function (err, data) {
          if (err) {
            return next(err);
          }
          res.json({
            data: data
          });
          next();
        });

      }
    );
});

var ipRegex = /(^(?:[0-9]{1,3}\.){3}[0-9]{1,3})/;
server.post('/logs', function (req, res, next) {
  console.log('POST /logs');
  res.send('ok');
  var payload = JSON.parse(req.params.payload);
  eachSeries(
    payload.events,
    function (event, callback) {
      var message = event.message;
      var matches = ipRegex.exec(message);
      if (matches !== null) {
        var ip = matches[1];
        getGeoLocation(ip, function (err, data) {
          if (err) {
            return next(err);
          }
          console.log(data, message);
          callback();
        });
      }
    },
    function (err) {
      if (err) {
        return next(err);
      }
      console.log('finished parsing ip locations');
      next();
    }
  );
});

function getGeoLocation (ip, callback) {
  request
    .post('https://www.iplocation.net/')
    .send('query=' + ip)
    .send('submit=IP Lookup')
    .end(
      function (err, lookupResponseHTML) {
        if (err) {
          return callback(err);
        }
        parseIpPage(lookupResponseHTML.text, callback);
      }
    );
}

function parseIpPage (html, callback) {
  var data = {};
  jsdom.env({
    html: html,
    scripts: ['https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js'],
    done: function (err, window) {
      if (err) {
        return callback(err);
      }
      data.ip = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(2) > tr > td:nth-child(1)').html();
      data.region = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(2) > tr > td:nth-child(3)').html();
      data.city = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(2) > tr > td:nth-child(4)').html();
      data.isp = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(4) > tr > td:nth-child(1)').html();
      data.organization = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(4) > tr > td:nth-child(2)').html();
      callback(null, data);
    }
  });
}

var serverPort = process.env.SERVER_PORT || 8080;
server.listen(serverPort, function () {
  console.log('geo-lookup server started on port ' + serverPort)
});
<commit_msg>send message to slack for each parsed event<commit_after>var restify = require('restify');
var request = require('superagent');
var jsdom = require('jsdom');
var eachSeries = require('async/eachSeries');

var server = restify.createServer({
  name: 'sempre geo lookup',
  version: '1.0.0'
});

server.use(restify.queryParser());
server.use(restify.bodyParser());

server.get('/', function (req, res, next) {
  console.log('GET /');
  var ip = '12.201.135.66';
  request
    .post('https://www.iplocation.net/')
    .send('query=' + ip)
    .send('submit=IP Lookup')
    .end(
      function (err, lookupResponseHTML) {
        if (err) {
          return next(err);
        }

        parseIpPage(lookupResponseHTML.text, function (err, data) {
          if (err) {
            return next(err);
          }
          res.json({
            data: data
          });
          next();
        });

      }
    );
});

var ipRegex = /(^(?:[0-9]{1,3}\.){3}[0-9]{1,3})/;
server.post('/logs', function (req, res, next) {
  console.log('POST /logs');
  res.send('ok');
  var payload = JSON.parse(req.params.payload);
  console.log('parsing ' + payload.events.length + ' new events');
  eachSeries(
    payload.events,
    function (event, callback) {
      var message = event.message;
      console.log('parsing message: ' + message);
      var matches = ipRegex.exec(message);
      if (matches !== null) {
        var ip = matches[1];
        getGeoLocation(ip, function (err, data) {
          if (err) {
            return next(err);
          }
          postToSlack(data, message, callback);
        });
      }
    },
    function (err) {
      if (err) {
        console.log('Error: ', err);
        return next(err);
      }
      console.log('finished handling one pager logs');
      next();
    }
  );
});

function getGeoLocation (ip, callback) {
  request
    .post('https://www.iplocation.net/')
    .send('query=' + ip)
    .send('submit=IP Lookup')
    .end(
      function (err, lookupResponseHTML) {
        if (err) {
          return callback(err);
        }
        console.log('parsed geolocation for ip ' + ip);
        parseIpPage(lookupResponseHTML.text, callback);
      }
    );
}

function parseIpPage (html, callback) {
  var data = {};
  jsdom.env({
    html: html,
    scripts: ['https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js'],
    done: function (err, window) {
      if (err) {
        return callback(err);
      }
      data.ip = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(2) > tr > td:nth-child(1)').html();
      data.region = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(2) > tr > td:nth-child(3)').html();
      data.city = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(2) > tr > td:nth-child(4)').html();
      data.isp = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(4) > tr > td:nth-child(1)').html();
      data.organization = window.$('#wrapper > section > div > div > div.col.col_8_of_12 > div:nth-child(9) > div > table > tbody:nth-child(4) > tr > td:nth-child(2)').html();
      console.log('parsed ip from page: ' + JSON.stringify(data));
      callback(null, data);
    }
  });
}

function postToSlack (data, message, callback) {
  var text = 'One Pager / Study Design accessed\n' +
    'Region: ' + data.region + '\n' +
    'City: ' + data.city + '\n' +
    'ISP: ' + data.isp + '\n' +
    'Org: ' + data.organization + '\n' +
    'Original Message: ' + message;
  request
    .post(process.env.SLACK_ENDPOINT)
    .type('json')
    .send({
      channel: '@swaraj',
      text: text
    })
    .end(
      function (err) {
        if (err) {
          return callback(err);
        }
        console.log('Posted message to slack');
        callback();
      }
    );
}

var serverPort = process.env.SERVER_PORT || 8080;
server.listen(serverPort, function () {
  console.log('geo-lookup server started on port ' + serverPort)
});
<|endoftext|>