<commit_before>import * as ACTION_TYPES from '../../src/constants/action_types'
import { camelizeKeys } from 'humps'
import 'isomorphic-fetch'

function getAuthToken() {
  return {
    'Authorization': `Bearer ${localStorage.getItem('ello_access_token')}`,
  }
}

function getPostJsonHeader() {
  return {
    ...getAuthToken(),
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  }
}

function getGetHeader() {
  return getAuthToken()
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  }
  const error = new Error(response.statusText)
  error.response = response
  throw error
}

function parseJSON(response) {
  // 200 means we have a body otherwise it's a 200+ with an empty body
  return (response.status === 200) ? response.json() : response
}

export function requester() {
  return next => action => {
    const { payload, type, meta } = action

    // This is problematic... :(
    if ((type !== ACTION_TYPES.LOAD_STREAM &&
         type !== ACTION_TYPES.POST_JSON &&
         type !== ACTION_TYPES.PROFILE.LOAD &&
         type !== ACTION_TYPES.PROFILE.SAVE &&
         type !== ACTION_TYPES.POST_FORM
        ) || !payload) {
      return next(action)
    }

    const { endpoint, method, body } = payload

    if (!endpoint) return next(action);

    const REQUEST = type + '_REQUEST'
    const SUCCESS = type + '_SUCCESS'
    const FAILURE = type + '_FAILURE'

    // dispatch the start of the request
    next({ type: REQUEST, payload, meta: meta })

    return fetch(endpoint, {
      method: method || 'GET',
      body: body || null,
      headers: (!method || method === 'GET') ? getGetHeader() : getPostJsonHeader(),
    })
      .then(checkStatus)
      .then(parseJSON)
      .then(response => {
        payload.response = camelizeKeys(response)
        next({ meta, payload, type: SUCCESS })
        return true
      })
      .catch(error => {
        if (error.response.status === 401) {
          window.resetAuth()
        }
        next({ error, meta, payload, type: FAILURE })
        return false
      })
  }
}

<commit_msg>Don’t send a body with the HEAD request<commit_after>import * as ACTION_TYPES from '../../src/constants/action_types'
import { camelizeKeys } from 'humps'
import 'isomorphic-fetch'

function getAuthToken() {
  return {
    'Authorization': `Bearer ${localStorage.getItem('ello_access_token')}`,
  }
}

function getPostJsonHeader() {
  return {
    ...getAuthToken(),
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  }
}

function getGetHeader() {
  return getAuthToken()
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  }
  const error = new Error(response.statusText)
  error.response = response
  throw error
}

function parseJSON(response) {
  // 200 means we have a body otherwise it's a 200+ with an empty body
  return (response.status === 200) ? response.json() : response
}

export function requester() {
  return next => action => {
    const { payload, type, meta } = action

    // This is problematic... :(
    if ((type !== ACTION_TYPES.LOAD_STREAM &&
         type !== ACTION_TYPES.POST_JSON &&
         type !== ACTION_TYPES.PROFILE.LOAD &&
         type !== ACTION_TYPES.PROFILE.SAVE &&
         type !== ACTION_TYPES.POST_FORM
        ) || !payload) {
      return next(action)
    }

    const { endpoint, method, body } = payload

    if (!endpoint) return next(action);

    const REQUEST = type + '_REQUEST'
    const SUCCESS = type + '_SUCCESS'
    const FAILURE = type + '_FAILURE'

    // dispatch the start of the request
    next({ type: REQUEST, payload, meta: meta })

    const options = {
      method: method || 'GET',
      headers: (!method || method === 'GET') ? getGetHeader() : getPostJsonHeader(),
    }

    if (options.method !== 'GET' && options.method !== 'HEAD') {
      options.body = body || null
    }

    return fetch(endpoint, options)
      .then(checkStatus)
      .then(parseJSON)
      .then(response => {
        payload.response = camelizeKeys(response)
        next({ meta, payload, type: SUCCESS })
        return true
      })
      .catch(error => {
        if (error.response.status === 401) {
          window.resetAuth()
        }
        next({ error, meta, payload, type: FAILURE })
        return false
      })
  }
}

<|endoftext|>


<commit_before>function fileToArray(file) {
  return (
    fs.readFileSync(__dirname + '/' +file)
      .toString()
      .trim()
      .split('\n')
  );
}

const sandwich = require('sandwich');
const fs = require('fs');
const ADVERBS = fileToArray('adverbs.txt');
const ADJECTIVES = fileToArray('adjectives.txt');
const NOUNS = fileToArray('nouns.txt');
const iterator = sandwich(ADVERBS, ADJECTIVES, NOUNS);

function phrases(count) {
  const matches = {};
  const results = [];
  var length = 0;
  var word;
  while (length < count) {
    word = iterator.random().join('-');
    if (!matches[word]) {
      matches[word] = results.push(word);
      length++;
    }
  }
  return results;
};
phrases.iterator = iterator;
module.exports = phrases;
<commit_msg>Use path.join instead of concating directories.<commit_after>const path = require('path');
const sandwich = require('sandwich');
const fs = require('fs');

function fileToArray(file) {
  return (
    fs.readFileSync(path.join(__dirname, '/', file))
      .toString()
      .trim()
      .split('\n')
  );
}

const ADVERBS = fileToArray('adverbs.txt');
const ADJECTIVES = fileToArray('adjectives.txt');
const NOUNS = fileToArray('nouns.txt');
const iterator = sandwich(ADVERBS, ADJECTIVES, NOUNS);

function phrases(count) {
  const matches = {};
  const results = [];
  var length = 0;
  var word;
  while (length < count) {
    word = iterator.random().join('-');
    if (!matches[word]) {
      matches[word] = results.push(word);
      length++;
    }
  }
  return results;
}

phrases.iterator = iterator;
module.exports = phrases;
<|endoftext|>


<commit_before>/*

    Store.js - in-memory store which mocks some very simple redis commands for local development / testing

    Important: You should not be using this Store in production settings.
               Instead of using the default memory store, pass in a node-redis client instead

*/

var Store = function Store (provider, plugin) {
  var self = this;
  self.services = {};
  self.files = {};
  console.log('Warning: ' + plugin + ' is using default memory store. This is not suitable for production. Instead pass in a node-redis client.');
  return self;
};
module.exports = Store;
Store.prototype.get = function (service, cb) {
  cb(null, this.services[service] || 0)
};
Store.prototype.set = function (key, val, cb) {
  this.services[key] = val;
  cb(null, true)
};

Store.prototype.mget = function (services, cb) {
  var res = [], self = this;
  services.forEach(function(s){
    self.get(s, function(err, r){
      res.push(r);
    })
  });
  cb(null, res)
};

Store.prototype.incr = function (service) {
  // callback not required, fire and forget to update value
  this.services[service] = this.services[service] || 0;
  this.services[service] += 1;
};

Store.prototype.incrby = function (service, by) {
  if (typeof by === "undefined") {
    by = 1;
  }
  this.services[service] = this.services[service] || 0;
  this.services[service] += by;
};

//
// Important: Redis sorted set commands ( zscore and zincrby ) are mocked here,
//            and will not perform a true sorted set operation.
//            These methods are only used for local dev and testing.
//
Store.prototype.zscore = function (zset, member, cb) {
  this.get(zset + '/' + member, cb)
};
Store.prototype.zincrby = function (args, cb) {
  if (typeof cb !== "function") {
    cb = function (err, res) {
    }
  }
  this.incrby(args[0] + "/" + args[2], args[1])
  cb(null);
};

Store.prototype.sadd = function (key, value, cb) {
  this.services[key] = this.services[key] || [];
  // Note: will always add new and not overwrite ( this is not standard sadd behavior )
  this.services[key].push(value);
  cb(null, 'added');
};

Store.prototype.smembers = function (key, cb) {
  cb(null, this.services[key]);
};<commit_msg>[api] Adds new methods to built-in memory store<commit_after>/*

    Store.js - in-memory store which mocks some very simple redis commands for local development / testing

    Important: You should not be using this Store in production settings.
               Instead of using the default memory store, pass in a node-redis client instead

*/

var Store = function Store (provider, plugin) {
  var self = this;
  self.services = {};
  self.files = {};
  console.log('Warning: ' + plugin + ' is using default memory store. This is not suitable for production. Instead pass in a node-redis client.');
  return self;
};
module.exports = Store;
Store.prototype.get = function (service, cb) {
  cb(null, this.services[service] || 0)
};
Store.prototype.set = function (key, val, cb) {
  this.services[key] = val;
  cb(null, true)
};

Store.prototype.mget = function (services, cb) {
  var res = [], self = this;
  services.forEach(function(s){
    self.get(s, function(err, r){
      res.push(r);
    })
  });
  cb(null, res)
};

Store.prototype.incr = function (service) {
  // callback not required, fire and forget to update value
  this.services[service] = this.services[service] || 0;
  this.services[service] += 1;
};

Store.prototype.incrby = function (service, by) {
  if (typeof by === "undefined") {
    by = 1;
  }
  this.services[service] = this.services[service] || 0;
  this.services[service] += by;
};

//
// Important: Redis sorted set commands ( zscore and zincrby ) are mocked here,
//            and will not perform a true sorted set operation.
//            These methods are only used for local dev and testing.
//
Store.prototype.zscore = function (zset, member, cb) {
  this.get(zset + '/' + member, cb)
};
Store.prototype.zincrby = function (args, cb) {
  if (typeof cb !== "function") {
    cb = function (err, res) {
    }
  }
  this.incrby(args[0] + "/" + args[2], args[1])
  cb(null);
};

Store.prototype.sadd = function (key, value, cb) {
  this.services[key] = this.services[key] || [];
  // Note: will always add new and not overwrite ( this is not standard sadd behavior )
  this.services[key].push(value);
  cb(null, 'added');
};

Store.prototype.smembers = function (key, cb) {
  cb(null, this.services[key]);
};

//
// hash type
//

/*
Store.prototype.hdel = function hdel (key, cb) {
  cb(null, this.services[key]);
};
*/

Store.prototype.hget = function hget (key, field, cb) {
  if (typeof this.services[key] === 'undefined') {
    return cb(null, 0);
  }
  cb(null, this.services[key][field]);
};

Store.prototype.hkeys = function hkeys (key, cb) {
  cb(null, Object.keys(this.services[key]));
};

Store.prototype.hset = function hset (key, field, value, cb) {
  this.services[key] = this.services[key] || {};
  this.services[key][field] = value;
  cb(null, 1);
};

Store.prototype.hgetall = function hget (key, cb) {
  cb(null, this.services[key]);
};

Store.prototype.hincrby = function hget (key, field, value, cb) {
  this.services[key] = this.services[key] || {};
  if (typeof this.services[key][field] !== 'number') {
    this.services[key][field] = 0;
  }
  this.services[key][field] = this.services[key][field] + value; // could be negative value to decrement
  cb(null, 1);
};<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var times = require('../times');

var offset = times.mins(2.5).msecs;
var bucketFields = ['index', 'fromMills', 'toMills'];

function init (ctx) {

  var translate = ctx.language.translate;

  var bgnow = {
    name: 'bgnow'
    , label: 'BG Now'
    , pluginType: 'pill-primary'
  };

  bgnow.mostRecentBucket = function mostRecentBucket (buckets) {
    return _.find(buckets, function notEmpty(bucket) {
      return bucket && !bucket.isEmpty;
    });
  };

  bgnow.previousBucket = function previousBucket(recent, buckets) {
    var previous = null;

    if (_.isObject(recent)) {
      previous = _.chain(buckets).find(function afterFirstNotEmpty(bucket) {
        return bucket.mills < recent.mills && !bucket.isEmpty;
      }).value();
    }

    return previous;
  };

  bgnow.setProperties = function setProperties (sbx) {
    var buckets = bgnow.fillBuckets(sbx);
    var recent = bgnow.mostRecentBucket(buckets);
    var previous = bgnow.previousBucket(recent, buckets);
    var delta = bgnow.calcDelta(recent, previous, sbx);

    sbx.offerProperty('bgnow', function setBGNow ( ) {
      return _.omit(recent, bucketFields);
    });

    sbx.offerProperty('delta', function setBGNow ( ) {
      return delta;
    });

    sbx.offerProperty('buckets', function setBGNow ( ) {
      return buckets;
    });
  };

  bgnow.fillBuckets = function fillBuckets (sbx, opts) {

    var bucketCount = (opts && opts.bucketCount) || 4;
    var bucketMins = (opts && opts.bucketMins) || 5;
    var bucketMsecs = times.mins(bucketMins).msecs;

    var lastSGVMills = sbx.lastSGVMills();

    var buckets = _.times(bucketCount, function createBucket (index) {
      var fromMills = lastSGVMills - offset - (index * bucketMsecs);
      return {
        index: index
        , fromMills: fromMills
        , toMills: fromMills + bucketMsecs
        , sgvs: [ ]
      };
    });

    _.takeRightWhile(sbx.data.sgvs, function addToBucket (sgv) {

      //if in the future, return true and keep taking right
      if (sgv.mills > sbx.time) {
        return true;
      }

      var bucket = _.find(buckets, function containsSGV (bucket) {
        return sgv.mills >= bucket.fromMills && sgv.mills <= bucket.toMills;
      });

      if (bucket) {
        bucket.sgvs.push(sgv);
      }

      return bucket;
    });

    return _.map(buckets, bgnow.analyzeBucket);
  };

  function notError (entry) {
    return entry && entry.mgdl > 39; //TODO maybe lower instead of expecting dexcom?
  }

  function isError (entry) {
    return !entry || !entry.mgdl || entry.mgdl < 39;
  }

  bgnow.analyzeBucket = function analyzeBucket (bucket) {

    if (_.isEmpty(bucket.sgvs)) {
      bucket.isEmpty = true;
      return bucket;
    }

    var details = { };

    var sgvs = _.filter(bucket.sgvs, notError);

    function calcMean ( ) {
      var sum = 0;
      _.forEach(sgvs, function eachSGV (sgv) {
        sum += Number(sgv.mgdl);
      });

      return sum / sgvs.length;
    }

    var mean = calcMean(sgvs);

    if (mean && _.isNumber(mean)) {
      details.mean = mean;
    }

    var mostRecent = _.maxBy(sgvs, 'mills');

    if (mostRecent) {
      details.last = mostRecent.mgdl;
      details.mills = mostRecent.mills;
    }

    var errors = _.filter(bucket.sgvs, isError);
    if (!_.isEmpty(errors)) {
      details.errors = errors;
    }

    return _.merge(details, bucket);
  };

  bgnow.calcDelta = function calcDelta (recent, previous, sbx) {

    if (_.isEmpty(recent)) {
      console.info('all buckets are empty');
      return null;
    }

    if (_.isEmpty(previous)) {
      console.info('previous bucket not found, not calculating delta');
      return null;
    }

    var delta = {
      absolute: recent.mean - previous.mean
      , elapsedMins: (recent.mills - previous.mills) / times.min().msecs
    };

    delta.interpolated = delta.elapsedMins > 9;

    delta.mean5MinsAgo = delta.interpolated ?
      recent.mean - delta.absolute / delta.elapsedMins * 5 : recent.mean - delta.absolute;

    delta.mgdl = Math.round(recent.mean - delta.mean5MinsAgo);

    delta.scaled = sbx.settings.units === 'mmol' ?
      sbx.roundBGToDisplayFormat(sbx.scaleMgdl(recent.mean) - sbx.scaleMgdl(delta.mean5MinsAgo)) : delta.mgdl;

    delta.display = (delta.scaled >= 0 ? '+' : '') + delta.scaled;

    delta.previous = _.omit(previous, bucketFields);

    return delta;

  };

  bgnow.updateVisualisation = function updateVisualisation (sbx) {
    var delta = sbx.properties.delta;

    var info = [];
    var display = delta && delta.display;

    if (delta && delta.interpolated) {
      display += ' *';
      info.push({label: translate('Elapsed Time'), value: Math.round(delta.elapsedMins) + ' ' + translate('mins')});
      info.push({label: translate('Absolute Delta'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.absolute)) + ' ' + sbx.unitsLabel});
      info.push({label: translate('Interpolated'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.mean5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    sbx.pluginBase.updatePillText({
      name: 'delta'
      , label: translate('BG Delta')
      , pluginType: 'pill-major'
      , pillFlip: true
    }, {
      value: display
      , label: sbx.unitsLabel
      , info: info
    });
  };

  function bgRollupHandler (slots, sbx, callback) {
    var message = 'Your blood glucose is ' + getBG(sbx) + '.';
    callback(null, {results: message, priority: 2});
  }

  function bgIntentHandler (callback, slots, sbx) {
    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\'s', '') + ' is ' : 'Your blood glucose is ';
    var message = preamble + getBG(sbx) + '.';
    callback('BG Status', message);
  }

  function getBG(sbx) {
    if (sbx.properties.iob && sbx.properties.bgnow.mean !== 0) {
      return sbx.properties.bgnow.mean;
    }
    return 'unknown';
  }

  bgnow.alexa = {
    rollupHandlers: [{
      rollupGroup: 'Status'
      , rollupName: 'bg status'
      , rollupHandler: bgRollupHandler
    }]
    , intentHandlers: [{
      intent: 'MetricNow'
      , routableSlot: 'metric'
      , slots: ['bg', 'blood glucose', 'number']
      , intentHandler: bgIntentHandler
    }]
  };

  return bgnow;

}

module.exports = init;
<commit_msg>Change callback to BG Status<commit_after>'use strict';

var _ = require('lodash');
var times = require('../times');

var offset = times.mins(2.5).msecs;
var bucketFields = ['index', 'fromMills', 'toMills'];

function init (ctx) {

  var translate = ctx.language.translate;

  var bgnow = {
    name: 'bgnow'
    , label: 'BG Now'
    , pluginType: 'pill-primary'
  };

  bgnow.mostRecentBucket = function mostRecentBucket (buckets) {
    return _.find(buckets, function notEmpty(bucket) {
      return bucket && !bucket.isEmpty;
    });
  };

  bgnow.previousBucket = function previousBucket(recent, buckets) {
    var previous = null;

    if (_.isObject(recent)) {
      previous = _.chain(buckets).find(function afterFirstNotEmpty(bucket) {
        return bucket.mills < recent.mills && !bucket.isEmpty;
      }).value();
    }

    return previous;
  };

  bgnow.setProperties = function setProperties (sbx) {
    var buckets = bgnow.fillBuckets(sbx);
    var recent = bgnow.mostRecentBucket(buckets);
    var previous = bgnow.previousBucket(recent, buckets);
    var delta = bgnow.calcDelta(recent, previous, sbx);

    sbx.offerProperty('bgnow', function setBGNow ( ) {
      return _.omit(recent, bucketFields);
    });

    sbx.offerProperty('delta', function setBGNow ( ) {
      return delta;
    });

    sbx.offerProperty('buckets', function setBGNow ( ) {
      return buckets;
    });
  };

  bgnow.fillBuckets = function fillBuckets (sbx, opts) {

    var bucketCount = (opts && opts.bucketCount) || 4;
    var bucketMins = (opts && opts.bucketMins) || 5;
    var bucketMsecs = times.mins(bucketMins).msecs;

    var lastSGVMills = sbx.lastSGVMills();

    var buckets = _.times(bucketCount, function createBucket (index) {
      var fromMills = lastSGVMills - offset - (index * bucketMsecs);
      return {
        index: index
        , fromMills: fromMills
        , toMills: fromMills + bucketMsecs
        , sgvs: [ ]
      };
    });

    _.takeRightWhile(sbx.data.sgvs, function addToBucket (sgv) {

      //if in the future, return true and keep taking right
      if (sgv.mills > sbx.time) {
        return true;
      }

      var bucket = _.find(buckets, function containsSGV (bucket) {
        return sgv.mills >= bucket.fromMills && sgv.mills <= bucket.toMills;
      });

      if (bucket) {
        bucket.sgvs.push(sgv);
      }

      return bucket;
    });

    return _.map(buckets, bgnow.analyzeBucket);
  };

  function notError (entry) {
    return entry && entry.mgdl > 39; //TODO maybe lower instead of expecting dexcom?
  }

  function isError (entry) {
    return !entry || !entry.mgdl || entry.mgdl < 39;
  }

  bgnow.analyzeBucket = function analyzeBucket (bucket) {

    if (_.isEmpty(bucket.sgvs)) {
      bucket.isEmpty = true;
      return bucket;
    }

    var details = { };

    var sgvs = _.filter(bucket.sgvs, notError);

    function calcMean ( ) {
      var sum = 0;
      _.forEach(sgvs, function eachSGV (sgv) {
        sum += Number(sgv.mgdl);
      });

      return sum / sgvs.length;
    }

    var mean = calcMean(sgvs);

    if (mean && _.isNumber(mean)) {
      details.mean = mean;
    }

    var mostRecent = _.maxBy(sgvs, 'mills');

    if (mostRecent) {
      details.last = mostRecent.mgdl;
      details.mills = mostRecent.mills;
    }

    var errors = _.filter(bucket.sgvs, isError);
    if (!_.isEmpty(errors)) {
      details.errors = errors;
    }

    return _.merge(details, bucket);
  };

  bgnow.calcDelta = function calcDelta (recent, previous, sbx) {

    if (_.isEmpty(recent)) {
      console.info('all buckets are empty');
      return null;
    }

    if (_.isEmpty(previous)) {
      console.info('previous bucket not found, not calculating delta');
      return null;
    }

    var delta = {
      absolute: recent.mean - previous.mean
      , elapsedMins: (recent.mills - previous.mills) / times.min().msecs
    };

    delta.interpolated = delta.elapsedMins > 9;

    delta.mean5MinsAgo = delta.interpolated ?
      recent.mean - delta.absolute / delta.elapsedMins * 5 : recent.mean - delta.absolute;

    delta.mgdl = Math.round(recent.mean - delta.mean5MinsAgo);

    delta.scaled = sbx.settings.units === 'mmol' ?
      sbx.roundBGToDisplayFormat(sbx.scaleMgdl(recent.mean) - sbx.scaleMgdl(delta.mean5MinsAgo)) : delta.mgdl;

    delta.display = (delta.scaled >= 0 ? '+' : '') + delta.scaled;

    delta.previous = _.omit(previous, bucketFields);

    return delta;

  };

  bgnow.updateVisualisation = function updateVisualisation (sbx) {
    var delta = sbx.properties.delta;

    var info = [];
    var display = delta && delta.display;

    if (delta && delta.interpolated) {
      display += ' *';
      info.push({label: translate('Elapsed Time'), value: Math.round(delta.elapsedMins) + ' ' + translate('mins')});
      info.push({label: translate('Absolute Delta'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.absolute)) + ' ' + sbx.unitsLabel});
      info.push({label: translate('Interpolated'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.mean5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    sbx.pluginBase.updatePillText({
      name: 'delta'
      , label: translate('BG Delta')
      , pluginType: 'pill-major'
      , pillFlip: true
    }, {
      value: display
      , label: sbx.unitsLabel
      , info: info
    });
  };

  function bgRollupHandler (slots, sbx, callback) {
    var message = 'Your blood glucose is ' + getBG(sbx) + '.';
    callback(null, {results: message, priority: 2});
  }

  function bgIntentHandler (callback, slots, sbx) {
    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\'s', '') + ' is ' : 'Your blood glucose is ';
    var message = preamble + getBG(sbx) + '.';
    callback('BG Status', message);
  }

  function getBG(sbx) {
    if (sbx.properties.iob && sbx.properties.bgnow.mean !== 0) {
      return sbx.properties.bgnow.mean;
    }
    return 'unknown';
  }

  bgnow.alexa = {
    rollupHandlers: [{
      rollupGroup: 'Status'
      , rollupName: 'BG status'
      , rollupHandler: bgRollupHandler
    }]
    , intentHandlers: [{
      intent: 'MetricNow'
      , routableSlot: 'metric'
      , slots: ['bg', 'blood glucose', 'number']
      , intentHandler: bgIntentHandler
    }]
  };

  return bgnow;

}

module.exports = init;
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var times = require('../times');

var offset = times.mins(2.5).msecs;
var bucketFields = ['index', 'fromMills', 'toMills'];

function init (ctx) {

  var translate = ctx.language.translate;

  var bgnow = {
    name: 'bgnow'
    , label: 'BG Now'
    , pluginType: 'pill-primary'
  };

  bgnow.mostRecentBucket = function mostRecentBucket (buckets) {
    return _.find(buckets, function notEmpty(bucket) {
      return bucket && !bucket.isEmpty;
    });
  };

  bgnow.previousBucket = function previousBucket(recent, buckets) {
    var previous = null;

    if (_.isObject(recent)) {
      previous = _.chain(buckets).find(function afterFirstNotEmpty(bucket) {
        return bucket.mills < recent.mills && !bucket.isEmpty;
      }).value();
    }

    return previous;
  };

  bgnow.setProperties = function setProperties (sbx) {
    var buckets = bgnow.fillBuckets(sbx);
    var recent = bgnow.mostRecentBucket(buckets);
    var previous = bgnow.previousBucket(recent, buckets);
    var delta = bgnow.calcDelta(recent, previous, sbx);

    sbx.offerProperty('bgnow', function setBGNow ( ) {
      return _.omit(recent, bucketFields);
    });

    sbx.offerProperty('delta', function setBGNow ( ) {
      return delta;
    });

    sbx.offerProperty('buckets', function setBGNow ( ) {
      return buckets;
    });
  };

  bgnow.fillBuckets = function fillBuckets (sbx, opts) {

    var bucketCount = (opts && opts.bucketCount) || 4;
    var bucketMins = (opts && opts.bucketMins) || 5;
    var bucketMsecs = times.mins(bucketMins).msecs;

    var lastSGVMills = sbx.lastSGVMills();

    var buckets = _.times(bucketCount, function createBucket (index) {
      var fromMills = lastSGVMills - offset - (index * bucketMsecs);
      return {
        index: index
        , fromMills: fromMills
        , toMills: fromMills + bucketMsecs
        , sgvs: [ ]
      };
    });

    _.takeRightWhile(sbx.data.sgvs, function addToBucket (sgv) {

      //if in the future, return true and keep taking right
      if (sgv.mills > sbx.time) {
        return true;
      }

      var bucket = _.find(buckets, function containsSGV (bucket) {
        return sgv.mills >= bucket.fromMills && sgv.mills <= bucket.toMills;
      });

      if (bucket) {
        bucket.sgvs.push(sgv);
      }

      return bucket;
    });

    return _.map(buckets, bgnow.analyzeBucket);
  };

  function notError (entry) {
    return entry && entry.mgdl > 39; //TODO maybe lower instead of expecting dexcom?
  }

  function isError (entry) {
    return !entry || !entry.mgdl || entry.mgdl < 39;
  }

  bgnow.analyzeBucket = function analyzeBucket (bucket) {

    if (_.isEmpty(bucket.sgvs)) {
      bucket.isEmpty = true;
      return bucket;
    }

    var details = { };

    var sgvs = _.filter(bucket.sgvs, notError);

    function calcMean ( ) {
      var sum = 0;
      _.forEach(sgvs, function eachSGV (sgv) {
        sum += Number(sgv.mgdl);
      });

      return sum / sgvs.length;
    }

    var mean = calcMean(sgvs);

    if (mean && _.isNumber(mean)) {
      details.mean = mean;
    }

    var mostRecent = _.maxBy(sgvs, 'mills');

    if (mostRecent) {
      details.last = mostRecent.mgdl;
      details.mills = mostRecent.mills;
    }

    var errors = _.filter(bucket.sgvs, isError);
    if (!_.isEmpty(errors)) {
      details.errors = errors;
    }

    return _.merge(details, bucket);
  };

  bgnow.calcDelta = function calcDelta (recent, previous, sbx) {

    if (_.isEmpty(recent)) {
      console.info('all buckets are empty');
      return null;
    }

    if (_.isEmpty(previous)) {
      console.info('previous bucket not found, not calculating delta');
      return null;
    }

    var delta = {
      absolute: recent.mean - previous.mean
      , elapsedMins: (recent.mills - previous.mills) / times.min().msecs
    };

    delta.interpolated = delta.elapsedMins > 9;

    delta.mean5MinsAgo = delta.interpolated ?
      recent.mean - delta.absolute / delta.elapsedMins * 5 : recent.mean - delta.absolute;

    delta.mgdl = Math.round(recent.mean - delta.mean5MinsAgo);

    delta.scaled = sbx.settings.units === 'mmol' ?
      sbx.roundBGToDisplayFormat(sbx.scaleMgdl(recent.mean) - sbx.scaleMgdl(delta.mean5MinsAgo)) : delta.mgdl;

    delta.display = (delta.scaled >= 0 ? '+' : '') + delta.scaled;

    delta.previous = _.omit(previous, bucketFields);

    return delta;

  };

  bgnow.updateVisualisation = function updateVisualisation (sbx) {
    var delta = sbx.properties.delta;

    var info = [];
    var display = delta && delta.display;

    if (delta && delta.interpolated) {
      display += ' *';
      info.push({label: translate('Elapsed Time'), value: Math.round(delta.elapsedMins) + ' ' + translate('mins')});
      info.push({label: translate('Absolute Delta'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.absolute)) + ' ' + sbx.unitsLabel});
      info.push({label: translate('Interpolated'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.mean5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    sbx.pluginBase.updatePillText({
      name: 'delta'
      , label: translate('BG Delta')
      , pluginType: 'pill-major'
      , pillFlip: true
    }, {
      value: display
      , label: sbx.unitsLabel
      , info: info
    });
  };

  return bgnow;

}

module.exports = init;
<commit_msg>Attempting to fix bgnow for Alexa<commit_after>'use strict';

var _ = require('lodash');
var times = require('../times');

var offset = times.mins(2.5).msecs;
var bucketFields = ['index', 'fromMills', 'toMills'];

function init (ctx) {

  var translate = ctx.language.translate;

  var bgnow = {
    name: 'bgnow'
    , label: 'BG Now'
    , pluginType: 'pill-primary'
  };

  bgnow.mostRecentBucket = function mostRecentBucket (buckets) {
    return _.find(buckets, function notEmpty(bucket) {
      return bucket && !bucket.isEmpty;
    });
  };

  bgnow.previousBucket = function previousBucket(recent, buckets) {
    var previous = null;

    if (_.isObject(recent)) {
      previous = _.chain(buckets).find(function afterFirstNotEmpty(bucket) {
        return bucket.mills < recent.mills && !bucket.isEmpty;
      }).value();
    }

    return previous;
  };

  bgnow.setProperties = function setProperties (sbx) {
    var buckets = bgnow.fillBuckets(sbx);
    var recent = bgnow.mostRecentBucket(buckets);
    var previous = bgnow.previousBucket(recent, buckets);
    var delta = bgnow.calcDelta(recent, previous, sbx);

    sbx.offerProperty('bgnow', function setBGNow ( ) {
      return _.omit(recent, bucketFields);
    });

    sbx.offerProperty('delta', function setBGNow ( ) {
      return delta;
    });

    sbx.offerProperty('buckets', function setBGNow ( ) {
      return buckets;
    });
  };

  bgnow.fillBuckets = function fillBuckets (sbx, opts) {

    var bucketCount = (opts && opts.bucketCount) || 4;
    var bucketMins = (opts && opts.bucketMins) || 5;
    var bucketMsecs = times.mins(bucketMins).msecs;

    var lastSGVMills = sbx.lastSGVMills();

    var buckets = _.times(bucketCount, function createBucket (index) {
      var fromMills = lastSGVMills - offset - (index * bucketMsecs);
      return {
        index: index
        , fromMills: fromMills
        , toMills: fromMills + bucketMsecs
        , sgvs: [ ]
      };
    });

    _.takeRightWhile(sbx.data.sgvs, function addToBucket (sgv) {

      //if in the future, return true and keep taking right
      if (sgv.mills > sbx.time) {
        return true;
      }

      var bucket = _.find(buckets, function containsSGV (bucket) {
        return sgv.mills >= bucket.fromMills && sgv.mills <= bucket.toMills;
      });

      if (bucket) {
        bucket.sgvs.push(sgv);
      }

      return bucket;
    });

    return _.map(buckets, bgnow.analyzeBucket);
  };

  function notError (entry) {
    return entry && entry.mgdl > 39; //TODO maybe lower instead of expecting dexcom?
  }

  function isError (entry) {
    return !entry || !entry.mgdl || entry.mgdl < 39;
  }

  bgnow.analyzeBucket = function analyzeBucket (bucket) {

    if (_.isEmpty(bucket.sgvs)) {
      bucket.isEmpty = true;
      return bucket;
    }

    var details = { };

    var sgvs = _.filter(bucket.sgvs, notError);

    function calcMean ( ) {
      var sum = 0;
      _.forEach(sgvs, function eachSGV (sgv) {
        sum += Number(sgv.mgdl);
      });

      return sum / sgvs.length;
    }

    var mean = calcMean(sgvs);

    if (mean && _.isNumber(mean)) {
      details.mean = mean;
    }

    var mostRecent = _.maxBy(sgvs, 'mills');

    if (mostRecent) {
      details.last = mostRecent.mgdl;
      details.mills = mostRecent.mills;
    }

    var errors = _.filter(bucket.sgvs, isError);
    if (!_.isEmpty(errors)) {
      details.errors = errors;
    }

    return _.merge(details, bucket);
  };

  bgnow.calcDelta = function calcDelta (recent, previous, sbx) {

    if (_.isEmpty(recent)) {
      console.info('all buckets are empty');
      return null;
    }

    if (_.isEmpty(previous)) {
      console.info('previous bucket not found, not calculating delta');
      return null;
    }

    var delta = {
      absolute: recent.mean - previous.mean
      , elapsedMins: (recent.mills - previous.mills) / times.min().msecs
    };

    delta.interpolated = delta.elapsedMins > 9;

    delta.mean5MinsAgo = delta.interpolated ?
      recent.mean - delta.absolute / delta.elapsedMins * 5 : recent.mean - delta.absolute;

    delta.mgdl = Math.round(recent.mean - delta.mean5MinsAgo);

    delta.scaled = sbx.settings.units === 'mmol' ?
      sbx.roundBGToDisplayFormat(sbx.scaleMgdl(recent.mean) - sbx.scaleMgdl(delta.mean5MinsAgo)) : delta.mgdl;

    delta.display = (delta.scaled >= 0 ? '+' : '') + delta.scaled;

    delta.previous = _.omit(previous, bucketFields);

    return delta;

  };

  bgnow.updateVisualisation = function updateVisualisation (sbx) {
    var delta = sbx.properties.delta;

    var info = [];
    var display = delta && delta.display;

    if (delta && delta.interpolated) {
      display += ' *';
      info.push({label: translate('Elapsed Time'), value: Math.round(delta.elapsedMins) + ' ' + translate('mins')});
      info.push({label: translate('Absolute Delta'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.absolute)) + ' ' + sbx.unitsLabel});
      info.push({label: translate('Interpolated'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.mean5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    sbx.pluginBase.updatePillText({
      name: 'delta'
      , label: translate('BG Delta')
      , pluginType: 'pill-major'
      , pillFlip: true
    }, {
      value: display
      , label: sbx.unitsLabel
      , info: info
    });
  };

  function alexaIOBIntentHandler (callback, slots, sbx) {
    var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\'s', '') + ' has ' : 'Your current bg is  ';
    var message = preamble + sbx.properties.delta + '';
    callback('Current bgnow', message);
  }

  function alexaIOBRollupHandler (slots, sbx, callback) {
    var message = 'and your current bg is ' + sbx.properties.delta + '.';
    callback(null, {results: message, priority: 2});
  }

  return bgnow;

}

module.exports = init;
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');

var offset = times.mins(2.5).msecs;
var bucketFields = ['index', 'fromMills', 'toMills'];

function init (ctx) {

  var translate = ctx.language.translate;
  var timeago = require('./timeago')(ctx);

  var bgnow = {
    name: 'bgnow'
    , label: 'BG Now'
    , pluginType: 'pill-primary'
  };

  bgnow.mostRecentBucket = function mostRecentBucket (buckets) {
    return _.find(buckets, function notEmpty(bucket) {
      return bucket && !bucket.isEmpty;
    });
  };

  bgnow.previousBucket = function previousBucket(recent, buckets) {
    var previous = null;

    if (_.isObject(recent)) {
      previous = _.chain(buckets).find(function afterFirstNotEmpty(bucket) {
        return bucket.mills < recent.mills && !bucket.isEmpty;
      }).value();
    }

    return previous;
  };

  bgnow.setProperties = function setProperties (sbx) {
    var buckets = bgnow.fillBuckets(sbx);
    var recent = bgnow.mostRecentBucket(buckets);
    var previous = bgnow.previousBucket(recent, buckets);
    var delta = bgnow.calcDelta(recent, previous, sbx);

    sbx.offerProperty('bgnow', function setBGNow ( ) {
      return _.omit(recent, bucketFields);
    });

    sbx.offerProperty('delta', function setBGNow ( ) {
      return delta;
    });

    sbx.offerProperty('buckets', function setBGNow ( ) {
      return buckets;
    });
  };

  bgnow.fillBuckets = function fillBuckets (sbx, opts) {

    var bucketCount = (opts && opts.bucketCount) || 4;
    var bucketMins = (opts && opts.bucketMins) || 5;
    var bucketMsecs = times.mins(bucketMins).msecs;

    var lastSGVMills = sbx.lastSGVMills();

    var buckets = _.times(bucketCount, function createBucket (index) {
      var fromMills = lastSGVMills - offset - (index * bucketMsecs);
      return {
        index: index
        , fromMills: fromMills
        , toMills: fromMills + bucketMsecs
        , sgvs: [ ]
      };
    });

    _.takeRightWhile(sbx.data.sgvs, function addToBucket (sgv) {

      //if in the future, return true and keep taking right
      if (sgv.mills > sbx.time) {
        return true;
      }

      var bucket = _.find(buckets, function containsSGV (bucket) {
        return sgv.mills >= bucket.fromMills && sgv.mills <= bucket.toMills;
      });

      if (bucket) {
        bucket.sgvs.push(sgv);
      }

      return bucket;
    });

    return _.map(buckets, bgnow.analyzeBucket);
  };

  function notError (entry) {
    return entry && entry.mgdl > 39; //TODO maybe lower instead of expecting dexcom?
  }

  function isError (entry) {
    return !entry || !entry.mgdl || entry.mgdl < 39;
  }

  bgnow.analyzeBucket = function analyzeBucket (bucket) {

    if (_.isEmpty(bucket.sgvs)) {
      bucket.isEmpty = true;
      return bucket;
    }

    var details = { };

    var sgvs = _.filter(bucket.sgvs, notError);

    function calcMean ( ) {
      var sum = 0;
      _.forEach(sgvs, function eachSGV (sgv) {
        sum += Number(sgv.mgdl);
      });

      return sum / sgvs.length;
    }

    var mean = calcMean(sgvs);

    if (mean && _.isNumber(mean)) {
      details.mean = mean;
    }

    var mostRecent = _.maxBy(sgvs, 'mills');

    if (mostRecent) {
      details.last = mostRecent.mgdl;
      details.mills = mostRecent.mills;
    }

    var errors = _.filter(bucket.sgvs, isError);
    if (!_.isEmpty(errors)) {
      details.errors = errors;
    }

    return _.merge(details, bucket);
  };

  bgnow.calcDelta = function calcDelta (recent, previous, sbx) {

    if (_.isEmpty(recent)) {
      console.info('all buckets are empty');
      return null;
    }

    if (_.isEmpty(previous)) {
      console.info('previous bucket not found, not calculating delta');
      return null;
    }

    var delta = {
      absolute: recent.mean - previous.mean
      , elapsedMins: (recent.mills - previous.mills) / times.min().msecs
    };

    delta.interpolated = delta.elapsedMins > 9;

    delta.mean5MinsAgo = delta.interpolated ?
      recent.mean - delta.absolute / delta.elapsedMins * 5 : recent.mean - delta.absolute;

    delta.mgdl = Math.round(recent.mean - delta.mean5MinsAgo);

    delta.scaled = sbx.settings.units === 'mmol' ?
      sbx.roundBGToDisplayFormat(sbx.scaleMgdl(recent.mean) - sbx.scaleMgdl(delta.mean5MinsAgo)) : delta.mgdl;

    delta.display = (delta.scaled >= 0 ? '+' : '') + delta.scaled;

    delta.previous = _.omit(previous, bucketFields);

    return delta;

  };

  bgnow.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.bgnow;
    var delta = sbx.properties.delta;

    var info = [];
    var display = delta && delta.display;

    if (delta && delta.interpolated) {
      display += ' *';
      info.push({label: translate('Elapsed Time'), value: Math.round(delta.elapsedMins) + ' ' + translate('mins')});
      info.push({label: translate('Absolute Delta'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.absolute)) + ' ' + sbx.unitsLabel});
      info.push({label: translate('Interpolated'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.mean5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    var deviceInfos = { };

    if (prop.sgvs) {
      _.forEach(prop.sgvs, function deviceAndValue(entry) {
        var device = deviceName(entry.device);
        deviceInfos[device] = {
          time: timeFormat(moment(entry.mills), sbx)
          , value: sbx.scaleEntry(entry)
          , recent: entry
        };
      });
    }

    if (delta.previous && delta.previous.sgvs) {
      _.forEach(delta.previous.sgvs, function deviceAndValue(entry) {
        var device = deviceName(entry.device);
        var deviceInfo = deviceInfos[device];
        if (deviceInfo) {
          var deviceDelta = bgnow.calcDelta(
            { mills: deviceInfo.recent.mills , mean: deviceInfo.recent.mgdl}
            , { mills: entry.mills, mean: entry.mgdl}
            , sbx
          );

          if (deviceDelta) {
            deviceInfo.delta = deviceDelta.display
          }
        } else {
          deviceInfos[device] = {
            time: timeFormat(moment(entry.mills), sbx)
            , value: sbx.scaleEntry(entry)
          };
        }
      });

      _.forIn(deviceInfos, function addInfo (deviceInfo, name) {
        var display = deviceInfo.value;
        if (deviceInfo.delta) {
          display += ' ' + deviceInfo.delta;
        }

        display += ' (' + deviceInfo.time + ')';

        info.push({label: name, value: display});
      });
    }

    sbx.pluginBase.updatePillText({
      name: 'delta'
      , label: translate('BG Delta')
      , pluginType: 'pill-major'
      , pillFlip: true
    }, {
      value: display
      , label: sbx.unitsLabel
      , info: _.isEmpty(info) ? null : info
    });
  };

  function deviceName (device) {
    var last = device ? _.last(device.split('://')) : 'unknown';
    return _.first(last.split('/'));
  }

  function timeFormat (m, sbx) {
    var when;
    if (m && sbx.data.inRetroMode) {
      when = m.format('LT');
    } else if (m) {
      when = formatAgo(m, sbx.time);
    } else {
      when = 'unknown';
    }

    return when;
  }

  function formatAgo (m, nowMills) {
    var ago = timeago.calcDisplay({mills: m.valueOf()}, nowMills);
    return translate('%1' + ago.shortLabel + (ago.shortLabel.length === 1 ? ' ago' : ''), { params: [(ago.value ? ago.value : '')]});
  }

  return bgnow;

}

module.exports = init;
<commit_msg>don't forget to check the length<commit_after>'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');

var offset = times.mins(2.5).msecs;
var bucketFields = ['index', 'fromMills', 'toMills'];

function init (ctx) {

  var translate = ctx.language.translate;
  var timeago = require('./timeago')(ctx);

  var bgnow = {
    name: 'bgnow'
    , label: 'BG Now'
    , pluginType: 'pill-primary'
  };

  bgnow.mostRecentBucket = function mostRecentBucket (buckets) {
    return _.find(buckets, function notEmpty(bucket) {
      return bucket && !bucket.isEmpty;
    });
  };

  bgnow.previousBucket = function previousBucket(recent, buckets) {
    var previous = null;

    if (_.isObject(recent)) {
      previous = _.chain(buckets).find(function afterFirstNotEmpty(bucket) {
        return bucket.mills < recent.mills && !bucket.isEmpty;
      }).value();
    }

    return previous;
  };

  bgnow.setProperties = function setProperties (sbx) {
    var buckets = bgnow.fillBuckets(sbx);
    var recent = bgnow.mostRecentBucket(buckets);
    var previous = bgnow.previousBucket(recent, buckets);
    var delta = bgnow.calcDelta(recent, previous, sbx);

    sbx.offerProperty('bgnow', function setBGNow ( ) {
      return _.omit(recent, bucketFields);
    });

    sbx.offerProperty('delta', function setBGNow ( ) {
      return delta;
    });

    sbx.offerProperty('buckets', function setBGNow ( ) {
      return buckets;
    });
  };

  bgnow.fillBuckets = function fillBuckets (sbx, opts) {

    var bucketCount = (opts && opts.bucketCount) || 4;
    var bucketMins = (opts && opts.bucketMins) || 5;
    var bucketMsecs = times.mins(bucketMins).msecs;

    var lastSGVMills = sbx.lastSGVMills();

    var buckets = _.times(bucketCount, function createBucket (index) {
      var fromMills = lastSGVMills - offset - (index * bucketMsecs);
      return {
        index: index
        , fromMills: fromMills
        , toMills: fromMills + bucketMsecs
        , sgvs: [ ]
      };
    });

    _.takeRightWhile(sbx.data.sgvs, function addToBucket (sgv) {

      //if in the future, return true and keep taking right
      if (sgv.mills > sbx.time) {
        return true;
      }

      var bucket = _.find(buckets, function containsSGV (bucket) {
        return sgv.mills >= bucket.fromMills && sgv.mills <= bucket.toMills;
      });

      if (bucket) {
        bucket.sgvs.push(sgv);
      }

      return bucket;
    });

    return _.map(buckets, bgnow.analyzeBucket);
  };

  function notError (entry) {
    return entry && entry.mgdl > 39; //TODO maybe lower instead of expecting dexcom?
  }

  function isError (entry) {
    return !entry || !entry.mgdl || entry.mgdl < 39;
  }

  bgnow.analyzeBucket = function analyzeBucket (bucket) {

    if (_.isEmpty(bucket.sgvs)) {
      bucket.isEmpty = true;
      return bucket;
    }

    var details = { };

    var sgvs = _.filter(bucket.sgvs, notError);

    function calcMean ( ) {
      var sum = 0;
      _.forEach(sgvs, function eachSGV (sgv) {
        sum += Number(sgv.mgdl);
      });

      return sum / sgvs.length;
    }

    var mean = calcMean(sgvs);

    if (mean && _.isNumber(mean)) {
      details.mean = mean;
    }

    var mostRecent = _.maxBy(sgvs, 'mills');

    if (mostRecent) {
      details.last = mostRecent.mgdl;
      details.mills = mostRecent.mills;
    }

    var errors = _.filter(bucket.sgvs, isError);
    if (!_.isEmpty(errors)) {
      details.errors = errors;
    }

    return _.merge(details, bucket);
  };

  bgnow.calcDelta = function calcDelta (recent, previous, sbx) {

    if (_.isEmpty(recent)) {
      console.info('all buckets are empty');
      return null;
    }

    if (_.isEmpty(previous)) {
      console.info('previous bucket not found, not calculating delta');
      return null;
    }

    var delta = {
      absolute: recent.mean - previous.mean
      , elapsedMins: (recent.mills - previous.mills) / times.min().msecs
    };

    delta.interpolated = delta.elapsedMins > 9;

    delta.mean5MinsAgo = delta.interpolated ?
      recent.mean - delta.absolute / delta.elapsedMins * 5 : recent.mean - delta.absolute;

    delta.mgdl = Math.round(recent.mean - delta.mean5MinsAgo);

    delta.scaled = sbx.settings.units === 'mmol' ?
      sbx.roundBGToDisplayFormat(sbx.scaleMgdl(recent.mean) - sbx.scaleMgdl(delta.mean5MinsAgo)) : delta.mgdl;

    delta.display = (delta.scaled >= 0 ? '+' : '') + delta.scaled;

    delta.previous = _.omit(previous, bucketFields);

    return delta;

  };

  bgnow.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.bgnow;
    var delta = sbx.properties.delta;

    var info = [];
    var display = delta && delta.display;

    if (delta && delta.interpolated) {
      display += ' *';
      info.push({label: translate('Elapsed Time'), value: Math.round(delta.elapsedMins) + ' ' + translate('mins')});
      info.push({label: translate('Absolute Delta'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.absolute)) + ' ' + sbx.unitsLabel});
      info.push({label: translate('Interpolated'), value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(delta.mean5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    var deviceInfos = { };

    if (prop.sgvs && prop.sgvs.length > 1) {
      _.forEach(prop.sgvs, function deviceAndValue(entry) {
        var device = deviceName(entry.device);
        deviceInfos[device] = {
          time: timeFormat(moment(entry.mills), sbx)
          , value: sbx.scaleEntry(entry)
          , recent: entry
        };
      });
    }

    if (delta.previous && delta.previous.sgvs && delta.previous.sgvs.length > 1) {
      _.forEach(delta.previous.sgvs, function deviceAndValue(entry) {
        var device = deviceName(entry.device);
        var deviceInfo = deviceInfos[device];
        if (deviceInfo) {
          var deviceDelta = bgnow.calcDelta(
            { mills: deviceInfo.recent.mills , mean: deviceInfo.recent.mgdl}
            , { mills: entry.mills, mean: entry.mgdl}
            , sbx
          );

          if (deviceDelta) {
            deviceInfo.delta = deviceDelta.display
          }
        } else {
          deviceInfos[device] = {
            time: timeFormat(moment(entry.mills), sbx)
            , value: sbx.scaleEntry(entry)
          };
        }
      });

      _.forIn(deviceInfos, function addInfo (deviceInfo, name) {
        var display = deviceInfo.value;
        if (deviceInfo.delta) {
          display += ' ' + deviceInfo.delta;
        }

        display += ' (' + deviceInfo.time + ')';

        info.push({label: name, value: display});
      });
    }

    sbx.pluginBase.updatePillText({
      name: 'delta'
      , label: translate('BG Delta')
      , pluginType: 'pill-major'
      , pillFlip: true
    }, {
      value: display
      , label: sbx.unitsLabel
      , info: _.isEmpty(info) ? null : info
    });
  };

  function deviceName (device) {
    var last = device ? _.last(device.split('://')) : 'unknown';
    return _.first(last.split('/'));
  }

  function timeFormat (m, sbx) {
    var when;
    if (m && sbx.data.inRetroMode) {
      when = m.format('LT');
    } else if (m) {
      when = formatAgo(m, sbx.time);
    } else {
      when = 'unknown';
    }

    return when;
  }

  function formatAgo (m, nowMills) {
    var ago = timeago.calcDisplay({mills: m.valueOf()}, nowMills);
    return translate('%1' + ago.shortLabel + (ago.shortLabel.length === 1 ? ' ago' : ''), { params: [(ago.value ? ago.value : '')]});
  }

  return bgnow;

}

module.exports = init;
<|endoftext|>


<commit_before>'use strict';

var ONE_MINUTE = 60000;

function init() {

  function delta() {
    return delta;
  }

  delta.label = 'BG Delta';
  delta.pluginType = 'pill-major';
  delta.pillFlip = true;

  delta.setProperties = function setProperties (sbx) {
    sbx.offerProperty('delta', function setDelta ( ) {
      return delta.calc(
        sbx.data.sgvs.length >= 2 ? sbx.data.sgvs[sbx.data.sgvs.length - 2] : null
        , sbx.data.sgvs.length >= 1 ? sbx.data.sgvs[sbx.data.sgvs.length - 1] : null
        , sbx
      );
    });
  };

  delta.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.delta;

    var info = [];
    var display = prop.display;

    if (prop.interpolated) {
      display += ' *';
      info.push({label: 'Elapsed Time', value: Math.round(prop.elapsedMins) + ' mins'});
      info.push({label: 'Absolute Delta', value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(prop.absMgdl)) + ' ' + sbx.unitsLabel});
      info.push({label: 'Interpolated', value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(prop.mgdl5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    sbx.pluginBase.updatePillText(delta, {
      value: display
      , label: sbx.unitsLabel
      , info: info
    });
  };

  delta.calc = function calc(prev, current, sbx) {
    var result = { display: null };

    if (!isSGVOk(prev) || !isSGVOk(current)) { return result; }

    result.absMgdl = current.mgdl - prev.mgdl;
    result.elapsedMins = (current.mills - prev.mills) / ONE_MINUTE;
    result.mgdl5MinsAgo = mgdl5MinsAgo(prev, current, result);
    result.interpolated = result.mgdl5MinsAgo !== prev.mgdl;
    result.mgdl = Math.round(current.mgdl - result.mgdl5MinsAgo);
    result.scaled = sbx.roundBGToDisplayFormat(sbx.scaleMgdl(result.mgdl));
    result.display = (result.scaled >= 0 ? '+' : '') + result.scaled;

    return result;
  };

  return delta();

}

function mgdl5MinsAgo (prev, current, result) {
  var mgdlAgo;
  if (result.elapsedMins > 10) {
    mgdlAgo = current.mgdl - result.absMgdl / result.elapsedMins * 5;
  } else {
    mgdlAgo = prev.mgdl;
  }
  return Math.round(mgdlAgo);
}

function isSGVOk (entry) {
  return entry && entry.mgdl >= 13;
}

module.exports = init;<commit_msg>update interpolation to start when gap is > 9min, set interpolated flag based on time not value<commit_after>'use strict';

var ONE_MINUTE = 60000;

function init() {

  function delta() {
    return delta;
  }

  delta.label = 'BG Delta';
  delta.pluginType = 'pill-major';
  delta.pillFlip = true;

  delta.setProperties = function setProperties (sbx) {
    sbx.offerProperty('delta', function setDelta ( ) {
      return delta.calc(
        sbx.data.sgvs.length >= 2 ? sbx.data.sgvs[sbx.data.sgvs.length - 2] : null
        , sbx.data.sgvs.length >= 1 ? sbx.data.sgvs[sbx.data.sgvs.length - 1] : null
        , sbx
      );
    });
  };

  delta.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.delta;

    var info = [];
    var display = prop.display;

    if (prop.interpolated) {
      display += ' *';
      info.push({label: 'Elapsed Time', value: Math.round(prop.elapsedMins) + ' mins'});
      info.push({label: 'Absolute Delta', value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(prop.absMgdl)) + ' ' + sbx.unitsLabel});
      info.push({label: 'Interpolated', value: sbx.roundBGToDisplayFormat(sbx.scaleMgdl(prop.mgdl5MinsAgo)) + ' ' + sbx.unitsLabel});
    }

    sbx.pluginBase.updatePillText(delta, {
      value: display
      , label: sbx.unitsLabel
      , info: info
    });
  };

  delta.calc = function calc(prev, current, sbx) {
    var result = { display: null };

    if (!isSGVOk(prev) || !isSGVOk(current)) { return result; }

    result.absMgdl = current.mgdl - prev.mgdl;
    result.elapsedMins = (current.mills - prev.mills) / ONE_MINUTE;

    updateWithInterpolation(prev, current, result);

    result.mgdl = Math.round(current.mgdl - result.mgdl5MinsAgo);
    result.scaled = sbx.roundBGToDisplayFormat(sbx.scaleMgdl(result.mgdl));
    result.display = (result.scaled >= 0 ? '+' : '') + result.scaled;

    return result;
  };

  return delta();

}

function updateWithInterpolation (prev, current, result) {
  result.interpolated = result.elapsedMins > 9;
  result.mgdl5MinsAgo = result.interpolated
    ? current.mgdl - result.absMgdl / result.elapsedMins * 5
    : result.mgdl5MinsAgo = prev.mgdl;
}

function isSGVOk (entry) {
  return entry && entry.mgdl >= 13;
}

module.exports = init;<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

function init() {

  var allPlugins = []
    , enabledPlugins = [];

  function plugins(name) {
    if (name) {
      return _.find(allPlugins, {name: name});
    } else {
      return plugins;
    }
  }

  plugins.base = require('./pluginbase');

  var clientDefaultPlugins = [
    require('./rawbg')()
    , require('./delta')()
    , require('./iob')()
    , require('./cob')()
    , require('./boluswizardpreview')()
    , require('./cannulaage')()
    , require('./basalprofile')()
    , require('./upbat')()
  ];

  var serverDefaultPlugins = [
    require('./rawbg')()
    , require('./ar2')()
    , require('./simplealarms')()
    , require('./errorcodes')()
    , require('./iob')()
    , require('./cob')()
    , require('./boluswizardpreview')()
    , require('./treatmentnotify')()
  ];

  plugins.registerServerDefaults = function registerServerDefaults() {
    plugins.register(serverDefaultPlugins);
    return plugins;
  };

  plugins.registerClientDefaults = function registerClientDefaults() {
    plugins.register(clientDefaultPlugins);
    return plugins;
  };

  plugins.register = function register(all) {
    _.forEach(all, function eachPlugin(plugin) {
      allPlugins.push(plugin);
    });
  };

  plugins.init = function init(envOrApp) {
    enabledPlugins = [];
    function isEnabled(plugin) {
      //TODO: unify client/server env/app
      var enable = envOrApp.enabledOptions || envOrApp.enable;
      return enable && enable.indexOf(plugin.name) > -1;
    }

    _.forEach(allPlugins, function eachPlugin(plugin) {
      plugin.enabled = isEnabled(plugin);
      if (plugin.enabled) {
        enabledPlugins.push(plugin);
      }
    });
    return plugins;
  };

  plugins.eachPlugin = function eachPlugin(f) {
    _.forEach(allPlugins, f);
  };

  plugins.eachEnabledPlugin = function eachEnabledPlugin(f) {
    _.forEach(enabledPlugins, f);
  };

  //these plugins are either always on or have custom settings
  plugins.specialPlugins = 'delta upbat rawbg';

  plugins.shownPlugins = function(sbx) {
    return _.filter(enabledPlugins, function filterPlugins(plugin) {
      return plugins.specialPlugins.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1);
    });
  };

  plugins.eachShownPlugins = function eachShownPlugins(sbx, f) {
    _.forEach(plugins.shownPlugins(sbx), f);
  };

  plugins.hasShownType = function hasShownType(pluginType, sbx) {
    return _.find(plugins.shownPlugins(sbx), function findWithType(plugin) {
      return plugin.pluginType == pluginType;
    }) != undefined;
  };

  plugins.setProperties = function setProperties(sbx) {
    plugins.eachEnabledPlugin( function eachPlugin (plugin) {
      plugin.setProperties && plugin.setProperties(sbx.withExtendedSettings(plugin));
    });
  };

  plugins.checkNotifications = function checkNotifications(sbx) {
    plugins.eachEnabledPlugin( function eachPlugin (plugin) {
      plugin.checkNotifications && plugin.checkNotifications(sbx.withExtendedSettings(plugin));
    });
  };

  plugins.updateVisualisations = function updateVisualisations(sbx) {
    plugins.eachShownPlugins(sbx, function eachPlugin(plugin) {
      plugin.updateVisualisation && plugin.updateVisualisation(sbx.withExtendedSettings(plugin));
    });
  };

  plugins.enabledPluginNames = function enabledPluginNames() {
    return _.map(enabledPlugins, function mapped(plugin) {
      return plugin.name;
    }).join(' ');
  };

  plugins.extendedClientSettings = function extendedClientSettings (allExtendedSettings) {
    var clientSettings = {};
    _.forEach(clientDefaultPlugins, function eachClientPlugin (plugin) {
      clientSettings[plugin.name] = allExtendedSettings[plugin.name];
    });
    return clientSettings;
  };

  return plugins();

}

module.exports = init;<commit_msg>added delta to the default server plugins for use in the notifications<commit_after>'use strict';

var _ = require('lodash');

function init() {

  var allPlugins = []
    , enabledPlugins = [];

  function plugins(name) {
    if (name) {
      return _.find(allPlugins, {name: name});
    } else {
      return plugins;
    }
  }

  plugins.base = require('./pluginbase');

  var clientDefaultPlugins = [
    require('./rawbg')()
    , require('./delta')()
    , require('./iob')()
    , require('./cob')()
    , require('./boluswizardpreview')()
    , require('./cannulaage')()
    , require('./basalprofile')()
    , require('./upbat')()
  ];

  var serverDefaultPlugins = [
    require('./rawbg')()
    , require('./delta')()
    , require('./ar2')()
    , require('./simplealarms')()
    , require('./errorcodes')()
    , require('./iob')()
    , require('./cob')()
    , require('./boluswizardpreview')()
    , require('./treatmentnotify')()
  ];

  plugins.registerServerDefaults = function registerServerDefaults() {
    plugins.register(serverDefaultPlugins);
    return plugins;
  };

  plugins.registerClientDefaults = function registerClientDefaults() {
    plugins.register(clientDefaultPlugins);
    return plugins;
  };

  plugins.register = function register(all) {
    _.forEach(all, function eachPlugin(plugin) {
      allPlugins.push(plugin);
    });
  };

  plugins.init = function init(envOrApp) {
    enabledPlugins = [];
    function isEnabled(plugin) {
      //TODO: unify client/server env/app
      var enable = envOrApp.enabledOptions || envOrApp.enable;
      return enable && enable.indexOf(plugin.name) > -1;
    }

    _.forEach(allPlugins, function eachPlugin(plugin) {
      plugin.enabled = isEnabled(plugin);
      if (plugin.enabled) {
        enabledPlugins.push(plugin);
      }
    });
    return plugins;
  };

  plugins.eachPlugin = function eachPlugin(f) {
    _.forEach(allPlugins, f);
  };

  plugins.eachEnabledPlugin = function eachEnabledPlugin(f) {
    _.forEach(enabledPlugins, f);
  };

  //these plugins are either always on or have custom settings
  plugins.specialPlugins = 'delta upbat rawbg';

  plugins.shownPlugins = function(sbx) {
    return _.filter(enabledPlugins, function filterPlugins(plugin) {
      return plugins.specialPlugins.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1);
    });
  };

  plugins.eachShownPlugins = function eachShownPlugins(sbx, f) {
    _.forEach(plugins.shownPlugins(sbx), f);
  };

  plugins.hasShownType = function hasShownType(pluginType, sbx) {
    return _.find(plugins.shownPlugins(sbx), function findWithType(plugin) {
      return plugin.pluginType == pluginType;
    }) != undefined;
  };

  plugins.setProperties = function setProperties(sbx) {
    plugins.eachEnabledPlugin( function eachPlugin (plugin) {
      plugin.setProperties && plugin.setProperties(sbx.withExtendedSettings(plugin));
    });
  };

  plugins.checkNotifications = function checkNotifications(sbx) {
    plugins.eachEnabledPlugin( function eachPlugin (plugin) {
      plugin.checkNotifications && plugin.checkNotifications(sbx.withExtendedSettings(plugin));
    });
  };

  plugins.updateVisualisations = function updateVisualisations(sbx) {
    plugins.eachShownPlugins(sbx, function eachPlugin(plugin) {
      plugin.updateVisualisation && plugin.updateVisualisation(sbx.withExtendedSettings(plugin));
    });
  };

  plugins.enabledPluginNames = function enabledPluginNames() {
    return _.map(enabledPlugins, function mapped(plugin) {
      return plugin.name;
    }).join(' ');
  };

  plugins.extendedClientSettings = function extendedClientSettings (allExtendedSettings) {
    var clientSettings = {};
    _.forEach(clientDefaultPlugins, function eachClientPlugin (plugin) {
      clientSettings[plugin.name] = allExtendedSettings[plugin.name];
    });
    return clientSettings;
  };

  return plugins();

}

module.exports = init;<|endoftext|>


<commit_before>'use strict';

var utils = require('../utils');

/**
 * The `views` plugin is used to bubble up events:
 *
 *     view => collection => app
 *
 * and to pass down sensible options:
 *
 *     app => collection => view
 */

module.exports = function(app, views, options) {
  if (!utils.isValid(views, 'templates-plugins-views', ['views'])) {
    return;
  }

  /**
   * If `renameKey` is not defined on collection options,
   * use the `app.renameKey` settings.
   */

  if (typeof views.options.renameKey !== 'function') {
    views.option('renameKey', function(key, view) {
      return app.renameKey(key, view);
    });
  }

  /**
   * Overwrite the collection's `extendView` method with
   * `app.extendView()`
   */

  utils.define(views, 'extendView', function() {
    return app.extendView.apply(app, arguments);
  });

  /**
   * Bubble up custom collection events to app, allowing the user
   * to do: `app.on('page',...)`, `app.on('partial', ...)` etc.
   */

  var singularName = views.options.inflection;
  if (singularName) {
    views.on(singularName, function() {
      app.emit.bind(app, singularName).apply(app, arguments);
    });
  }

  /**
   * Listen for `view`
   *  - bubble event from `views` up to `app`
   *  - set default engine if not defined
   *  - set default layout if not defined
   */

  views.on('view', function(view, collectionName, collection) {
    views.setType(view);

    // bind the `addView` method to allow chaining
    utils.define(view, 'addView', views.addView.bind(views));

    // pass the engine defined on `collection.options` to `view.options`
    if (!view.engine) {
      view.engine = views.options.engine;
    }
    app.extendView(view, options);
  });

  /**
   * Listen for `load`
   */

  views.on('load', function(view, collectionName, collection) {
    app.emit.bind(app, 'view').apply(app, arguments);
    app.emit.bind(app, 'load').apply(app, arguments);

    if (app.options.onLoad !== false
        && views.options.onLoad !== false
        && view.options.onLoad !== false) {
      app.handleOnce('onLoad', view);
    }
  });
};
<commit_msg>ensure `app` is not a collection<commit_after>'use strict';

var utils = require('../utils');

/**
 * The `views` plugin is used to bubble up events:
 *
 *     view => collection => app
 *
 * and to pass down sensible options:
 *
 *     app => collection => view
 */

module.exports = function(app, views, options) {
  if (!utils.isValid(views, 'templates-plugins-views', ['views'])) {
    return;
  }

  /**
   * If `renameKey` is not defined on collection options,
   * use the `app.renameKey` settings.
   */

  if (typeof views.options.renameKey !== 'function') {
    views.option('renameKey', function(key, view) {
      return app.renameKey(key, view);
    });
  }

  /**
   * Overwrite the collection's `extendView` method with
   * `app.extendView()`
   */

  utils.define(views, 'extendView', function() {
    return app.extendView.apply(app, arguments);
  });

  /**
   * Bubble up custom collection events to app, allowing the user
   * to do: `app.on('page',...)`, `app.on('partial', ...)` etc.
   */

  var singularName = views.options.inflection;
  if (singularName && app.isApp) {
    views.on(singularName, function() {
      app.emit.bind(app, singularName).apply(app, arguments);
    });
  }

  /**
   * Listen for `view`
   *  - bubble event from `views` up to `app`
   *  - set default engine if not defined
   *  - set default layout if not defined
   */

  views.on('view', function(view, collectionName, collection) {
    views.setType(view);

    // bind the `addView` method to allow chaining
    utils.define(view, 'addView', views.addView.bind(views));

    // pass the engine defined on `collection.options` to `view.options`
    if (!view.engine) {
      view.engine = views.options.engine;
    }
    app.extendView(view, options);
  });

  /**
   * Listen for `load`
   */

  views.on('load', function(view, collectionName, collection) {
    app.emit.bind(app, 'view').apply(app, arguments);
    app.emit.bind(app, 'load').apply(app, arguments);

    if (app.options.onLoad !== false
        && views.options.onLoad !== false
        && view.options.onLoad !== false) {
      app.handleOnce('onLoad', view);
    }
  });
};
<|endoftext|>


<commit_before>/** internal
 *  class Route
 **/

'use strict';

var URLBuilder = require('./route/url-builder');
var Compiler = require('./route/compiler');
var Common = require('./common');

// jshint workaround
var toObject = Object;

// returns param internal config definition
function define_param(idx, requiredFlag, defaultValue, re) {
  return {
    "idx":      idx || -1,
    "required": !!requiredFlag,
    "default":  defaultValue,
    "match_re": re || /[^\/]+?/
  };
}

// builds regexp recursively
function build_regexp(self, nodes) {
  var re = '';

  Common.each(nodes, function (node) {
    if ('string' === node.type) {
      // convert string to regexp-safe
      re += node.string.replace(/([.?*+{}()\[\]])/g, '\\$1');
      return;
    }

    if ('param' === node.type) {
      // initial state of self.__idx__ == 0 (no capture groups)
      // regexp's capture groups starts with 1
      self.__idx__ += 1;

      if (!self.__params__[node.key]) {
        // define param configuration if it was not passed within
        // params options to constructor
        self.__params__[node.key] = define_param(self.__idx__, true);
      } else {
        // FIXME: Throw an error on duplicate pattern param?
        self.__params__[node.key].idx = self.__idx__;
        self.__params__[node.key].required = true;
      }

      re += '(' + self.__params__[node.key].match_re.source + ')';
      return;
    }

    if ('optional' === node.type) {
      re += '(?:' + build_regexp(self, node.nodes) + ')?';
      return;
    }

    // THIS SHOULD NEVER HAPPEN!!!
    throw new Error('Unknown node type: "' + node.type + '".');
  });

  return re;
}

/**
 *  new Route(pattern[, params[, meta]])
 *  - pattern (String): URL pattern. See description below.
 *  - params (Object): Params options. Se description below.
 *  - meta (Mixed): Meta vars to be returned as part of MatchData
 *    by [[Route#match]] on successfull matching.
 *
 *  Route constructor.
 *
 *
 *  ##### Pattern
 *
 *  Pattern can be a simple string:
 *
 *      pattern: /my/very/simple/route
 *      regexp:  #/my/very/simple/route#
 *
 *  Or string with some parameter (surrounded with `{}`) substitutions:
 *
 *      pattern: /my/route/with/{some}/param
 *      regexp:  #/my/route/with/(?<some>[^/]+)/param#
 *
 *  Also it can have optional (surrounded with `()`) part:
 *
 *      pattern: /my/route/with(/optional)/part
 *      regexp:  #/my/route/with(?:/optional)?/part#
 *
 *  You may have as many params and optional groups as you want.
 *  You can even nest them:
 *
 *      pattern: /my/{kind_of}(-router)(.{format})
 *      regexp:  #/my/(?<kind_of>[^/]+)(?:-router)?(?:[.](?<format>[^/]+))?#
 *
 *
 *  ##### Params
 *
 *  Params are given in form of `param_name -> param_options` pairs. Options might
 *  be an Object (see Syntax Sugar below) or RegExp (shorthand for `{match: ...}`).
 *
 *  - *match* (Optional, RegExp, Default: [^/]+):
 *    RegExp that param should match
 *
 *  - *default* (Optional, Mixed):
 *    Default value, if param was not presented in the URL upon `match()`
 *    (when it was in the optional group or even not presentes in pattern).
 *
 *  You are free to specify as many `params` options as you want. You also can
 *  specify options of params that are not presented in matching rule, so their
 *  `default` values willl be used when matching route will be found, e.g.:
 *
 *      new Route('/t{thread_id}/last-page.html', {
 *        thread_id: /\d+/,
 *        page: -1
 *      }, my_handler);
 *
 *  You may specify param options as RegExp, in this case it will be equal to
 *  specifying only `match` option, these are equal:
 *
 *      {
 *        thread_id: /\d+/
 *      }
 *
 *      // shorthand syntax for:
 *
 *      {
 *        thread_id: {
 *          match: /\d+/
 *        }
 *      }
 *
 *  Also you can specify param options as `String` or `Integer`, in this case it
 *  will be equal to specifying `default` option only:
 *
 *      {
 *        page: 123
 *      }
 *
 *      // shorthand syntax for:
 *
 *      {
 *        page: {
 *          default: 123
 *        }
 *      }
 **/
var Route = module.exports = function Route(pattern, params, meta) {
  var ast = Compiler.compile(pattern);

  this.__idx__    = 0; // last param index
  this.__params__ = {};

  // prepare basic configuration of params
  Common.each(params, function (cfg, key) {
    var default_val, match_re;

    if ('[object RegExp]' === Object.prototype.toString.call(cfg)) {
      match_re = cfg;
    } else if (cfg !== toObject(cfg)) {
      default_val = cfg;
    } else {
      default_val = cfg['default'];
      match_re = cfg['match'];
    }

    this.__params__[key] = define_param(0, false, default_val, match_re);
  }, this);

  this.__regexp__   = new RegExp('^' + build_regexp(this, ast) + '$');
  this.__builder__  = new URLBuilder(ast);
  this.__meta__     = meta;
};

/**
 *  Route#match(url) -> Object|Null
 *  - url (String): URL to match route against
 *
 *  Returns _MatchData_ (see below) if [[Route]] matches against given `url`.
 *
 *
 *  ##### MatchedRoute
 *
 *  MatchedRoute is a structure similar to the one returned by [[Route#match]]
 *  but it also contains matched route instance as well:
 *
 *  - *route* ([[Route]]): Route self-reference.
 *  - *params* (Object): Params collected from the URL.
 *  - *meta* (Mixed): Meta data associated with route.
 **/
Route.prototype.match = function match(url) {
  var data, captures = String(url).match(this.__regexp__);

  if (captures) {
    data = {route: this, params: {}, meta: this.__meta__};

    // build params hash from capture groups
    Common.each(this.__params__, function (cfg, key) {
      var val = captures[cfg.idx];
      data.params[key] = ('undefined' === typeof val) ? cfg['default'] : val;
    });

    return data;
  }

  // route not match
  return null;
};

/**
 *  Route#isValidParams(params) -> Boolean
 *  - params (Object): Params to validate.
 *
 *  Tells whenever given `params` object is valid for route or not.
 **/
Route.prototype.isValidParams = function isValidParams(params) {
  var is_valid = true;

  // verify that given params contains all keys of pattern
  // and that they match associated regexp matchers
  Common.each(this.__params__, function (cfg, key) {
    if (cfg.required &&
        ('undefined' === typeof params[key] ||
         !cfg.match_re.test(params[key]))) {
      is_valid = false;
      return false;
    }
  });

  return is_valid;
};

/**
 *  Route#buildURL(params) -> String|Null
 *  - params (Object): Params to fill into resulting URL.
 *
 *  Returns URL representation of the route with given params.
 *  Returns `Null` when there were not enough params to build URL.
 **/
Route.prototype.buildURL = function buildURL(params) {
  return this.__builder__.build(params);
};
<commit_msg>Fix Route#isValidParams()<commit_after>/** internal
 *  class Route
 **/

'use strict';

var URLBuilder = require('./route/url-builder');
var Compiler = require('./route/compiler');
var Common = require('./common');

// jshint workaround
var toObject = Object;

// returns param internal config definition
function define_param(idx, requiredFlag, defaultValue, re) {
  return {
    "idx":      idx || -1,
    "required": !!requiredFlag,
    "default":  defaultValue,
    "match_re": re || /[^\/]+?/
  };
}

// builds regexp recursively
function build_regexp(self, nodes) {
  var re = '';

  Common.each(nodes, function (node) {
    if ('string' === node.type) {
      // convert string to regexp-safe
      re += node.string.replace(/([.?*+{}()\[\]])/g, '\\$1');
      return;
    }

    if ('param' === node.type) {
      // initial state of self.__idx__ == 0 (no capture groups)
      // regexp's capture groups starts with 1
      self.__idx__ += 1;

      if (!self.__params__[node.key]) {
        // define param configuration if it was not passed within
        // params options to constructor
        self.__params__[node.key] = define_param(self.__idx__, true);
      } else {
        // FIXME: Throw an error on duplicate pattern param?
        self.__params__[node.key].idx = self.__idx__;
        self.__params__[node.key].required = true;
      }

      re += '(' + self.__params__[node.key].match_re.source + ')';
      return;
    }

    if ('optional' === node.type) {
      re += '(?:' + build_regexp(self, node.nodes) + ')?';
      return;
    }

    // THIS SHOULD NEVER HAPPEN!!!
    throw new Error('Unknown node type: "' + node.type + '".');
  });

  return re;
}

/**
 *  new Route(pattern[, params[, meta]])
 *  - pattern (String): URL pattern. See description below.
 *  - params (Object): Params options. Se description below.
 *  - meta (Mixed): Meta vars to be returned as part of MatchData
 *    by [[Route#match]] on successfull matching.
 *
 *  Route constructor.
 *
 *
 *  ##### Pattern
 *
 *  Pattern can be a simple string:
 *
 *      pattern: /my/very/simple/route
 *      regexp:  #/my/very/simple/route#
 *
 *  Or string with some parameter (surrounded with `{}`) substitutions:
 *
 *      pattern: /my/route/with/{some}/param
 *      regexp:  #/my/route/with/(?<some>[^/]+)/param#
 *
 *  Also it can have optional (surrounded with `()`) part:
 *
 *      pattern: /my/route/with(/optional)/part
 *      regexp:  #/my/route/with(?:/optional)?/part#
 *
 *  You may have as many params and optional groups as you want.
 *  You can even nest them:
 *
 *      pattern: /my/{kind_of}(-router)(.{format})
 *      regexp:  #/my/(?<kind_of>[^/]+)(?:-router)?(?:[.](?<format>[^/]+))?#
 *
 *
 *  ##### Params
 *
 *  Params are given in form of `param_name -> param_options` pairs. Options might
 *  be an Object (see Syntax Sugar below) or RegExp (shorthand for `{match: ...}`).
 *
 *  - *match* (Optional, RegExp, Default: [^/]+):
 *    RegExp that param should match
 *
 *  - *default* (Optional, Mixed):
 *    Default value, if param was not presented in the URL upon `match()`
 *    (when it was in the optional group or even not presentes in pattern).
 *
 *  You are free to specify as many `params` options as you want. You also can
 *  specify options of params that are not presented in matching rule, so their
 *  `default` values willl be used when matching route will be found, e.g.:
 *
 *      new Route('/t{thread_id}/last-page.html', {
 *        thread_id: /\d+/,
 *        page: -1
 *      }, my_handler);
 *
 *  You may specify param options as RegExp, in this case it will be equal to
 *  specifying only `match` option, these are equal:
 *
 *      {
 *        thread_id: /\d+/
 *      }
 *
 *      // shorthand syntax for:
 *
 *      {
 *        thread_id: {
 *          match: /\d+/
 *        }
 *      }
 *
 *  Also you can specify param options as `String` or `Integer`, in this case it
 *  will be equal to specifying `default` option only:
 *
 *      {
 *        page: 123
 *      }
 *
 *      // shorthand syntax for:
 *
 *      {
 *        page: {
 *          default: 123
 *        }
 *      }
 **/
var Route = module.exports = function Route(pattern, params, meta) {
  var ast = Compiler.compile(pattern);

  this.__idx__    = 0; // last param index
  this.__params__ = {};

  // prepare basic configuration of params
  Common.each(params, function (cfg, key) {
    var default_val, match_re;

    if ('[object RegExp]' === Object.prototype.toString.call(cfg)) {
      match_re = cfg;
    } else if (cfg !== toObject(cfg)) {
      default_val = cfg;
    } else {
      default_val = cfg['default'];
      match_re = cfg['match'];
    }

    this.__params__[key] = define_param(0, false, default_val, match_re);
  }, this);

  this.__regexp__   = new RegExp('^' + build_regexp(this, ast) + '$');
  this.__builder__  = new URLBuilder(ast);
  this.__meta__     = meta;
};

/**
 *  Route#match(url) -> Object|Null
 *  - url (String): URL to match route against
 *
 *  Returns _MatchData_ (see below) if [[Route]] matches against given `url`.
 *
 *
 *  ##### MatchedRoute
 *
 *  MatchedRoute is a structure similar to the one returned by [[Route#match]]
 *  but it also contains matched route instance as well:
 *
 *  - *route* ([[Route]]): Route self-reference.
 *  - *params* (Object): Params collected from the URL.
 *  - *meta* (Mixed): Meta data associated with route.
 **/
Route.prototype.match = function match(url) {
  var data, captures = String(url).match(this.__regexp__);

  if (captures) {
    data = {route: this, params: {}, meta: this.__meta__};

    // build params hash from capture groups
    Common.each(this.__params__, function (cfg, key) {
      var val = captures[cfg.idx];
      data.params[key] = ('undefined' === typeof val) ? cfg['default'] : val;
    });

    return data;
  }

  // route not match
  return null;
};

/**
 *  Route#isValidParams(params) -> Boolean
 *  - params (Object): Params to validate.
 *
 *  Tells whenever given `params` object is valid for route or not.
 **/
Route.prototype.isValidParams = function isValidParams(params) {
  var is_valid = true;

  // make sure params is an object
  params = params || {};

  // verify that given params contains all keys of pattern
  // and that they match associated regexp matchers
  Common.each(this.__params__, function (cfg, key) {
    if (cfg.required &&
        ('undefined' === typeof params[key] ||
         !cfg.match_re.test(params[key]))) {
      is_valid = false;
      return false;
    }
  });

  return is_valid;
};

/**
 *  Route#buildURL(params) -> String|Null
 *  - params (Object): Params to fill into resulting URL.
 *
 *  Returns URL representation of the route with given params.
 *  Returns `Null` when there were not enough params to build URL.
 **/
Route.prototype.buildURL = function buildURL(params) {
  return this.__builder__.build(params);
};
<|endoftext|>