<commit_before>var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var urls = require('./web/urls');
var settings = require('./settings');
var schema = require('../extern/devopsjson/lib/web/schema').schema;
var JSV = require('JSV').JSV;
var _ = require('underscore');

// A bunch of enum like things for the stats push API (no hidden interfaces via magic strings!)
var HEALTH_UNKNOWN = 0;
var HEALTH_OK = 1;
var HEALTH_WARNING = 2;
var HEALTH_ERROR = 3;

// this sucks
exports.HEALTH_ERROR = HEALTH_ERROR;

var HEALTH_STRING_OK = 'OK';
var HEALTH_STRING_PROBLEM = 'PROBLEM';
var HEALTH_STRING_ERROR = 'ERROR';
var HEALTH_STRING_UNKNOWN = 'UNKNOWN';

exports.HEALTH_STRING_OK = HEALTH_STRING_OK;
exports.HEALTH_STRING_PROBLEM = HEALTH_STRING_PROBLEM;
exports.HEALTH_STRING_ERROR = HEALTH_STRING_ERROR;
exports.HEALTH_STRING_UNKNOWN = HEALTH_STRING_UNKNOWN;

exports.HEALTH_ENUM = {};
exports.HEALTH_ENUM[HEALTH_OK] = HEALTH_STRING_OK;
exports.HEALTH_ENUM[HEALTH_WARNING] = HEALTH_STRING_PROBLEM;
exports.HEALTH_ENUM[HEALTH_ERROR] = HEALTH_STRING_ERROR;
exports.HEALTH_ENUM[HEALTH_UNKNOWN] = HEALTH_STRING_UNKNOWN;

// build an array (of Numbers) to check against
var VALID_HEALTH = _.map(_.keys(exports.HEALTH_ENUM), function(n){return Number(n);});
exports.VALID_HEALTH = VALID_HEALTH;

/**
 * makeClass - By John Resig (MIT Licensed)
 * Takes a function and ensures that new is called so that __this__ is properly bound
 * @param {proto} optional prototype to set on the returned function
 */
exports.make_class = function(proto){
  var f = function(args){
    // did you make me with new?
    if (this instanceof arguments.callee){
      // am I a function?
      if (typeof this.init === "function"){
        //PREGUNTA: why not always pass apply arguments?
        if (args){
          return this.init.apply(this, args.callee ? args : arguments );
        }
        else{
          return this.init.apply(this, arguments);
        }
      }
    } else{
      // didn't use new, return a properly instantiated object
      return new arguments.callee(arguments);
    }
  };
  if (proto){
    f.prototype = proto;
  }
  return f;
};

/** A representation of the stats for a given host: see https://github.com/racker/gutsy/blob/master/docs/example_data_push.js
 * for the format of data
 */
var api_host_properties = exports.make_class({
  /** Constructor which takes a data blob
  * @param {object} data stats for a given host (see link above)
 */
  init: function(data){
    this.validate_data(data);
    _.extend(this, data);
  },
  /** Takes new data, validates it, and updates the local store of any fresh data
  * @param {object} data stats for a given host (see link above)
 */
  handle_new_data: function(data){
    var fresh, that;
    this.validate_data(data);

    fresh = false;
    if (data.timestamp >= this.timestamp){
      fresh = true;
      this.timestamp = data.timestamp;
      this.health = data.health;
      this.ip = data.ip;
    }

    that = this;
    _.each(data.services, function(service, service_name){
      var should_update_service = (!that.services[service_name]) ||
        (that.services[service_name].timestamp < service.timestamp);
      if (should_update_service){
        that.services[service_name] = service;
      }
    });
  },
  validate_data: function(data){
    var that = this;
    //TODO: use swiz for this
    if (!data.timestamp){
      throw new Error('You must specify a timestamp');
    }
    if (!data.name){
      throw new Error('You must specify a name');
    }
    data.timestamp = this._validate_time(data.timestamp);
    data.health = this._validate_health(data.health);
    _.each(data.services, function(service, service_name){
      service.timestamp = data.timestamp;
      service.health = that._validate_health(service.health);
    });
  },
  _validate_time: function(time_like){
    // have Date parse the time like thing and convert to a Number or bail
    return (new Date(time_like)).getTime();
  },
  _validate_health: function(health){
    if (VALID_HEALTH.indexOf(health) !== -1){
      return health;
    }
    throw new Error('Health status: ' + health + ' is not valid');
  }
});

/** A representation of the stats for all hosts.  This data is flushed to disk every settings.flush_stats_interval and
 * reloaded from disk at startup
 */
exports.api_cache = exports.make_class({
  init: function(){
    var that = this;
    var data = null;
    var flush_file;
    if(settings.testing){
      flush_file = settings.testing_flush_file_path;
    }else{
      flush_file = settings.flush_file_path;
    }
    this._flush_path = path.join(__dirname, flush_file);
    // try loading flushed data at boot
    try{
      data = fs.readFileSync(this._flush_path);
    } catch(e){
      // if this is the first run, the file may not exist (this is unsafe)
      if (e.code !== 'ENOENT'){
        console.warn('\nCouldn\'t load flushed data!\n');
        throw e;
      }
    }
    if (data !== null && data.length > 0){
      // if we have data, parse it and push into our cache
      data = JSON.parse(data);
      _.each(data, function(project, project_name){
        _.each(project, function(service){
          that.handle_push(project_name, service);
        });
      });
    }
  },
  /** Handles any new data for a given project.
  * @param {string} project the name of the project these stats correspond to
  * @param {object} host_data the given data for a host (see link in api_host_properties docs)
   */
  handle_push: function(project, host_data){
    if (!this._projects[project]){
      this._projects[project] = {};
    }
    if (!this._projects[project][host_data.name]){
      this._projects[project][host_data.name] = new api_host_properties(host_data);
    }
    this._projects[project][host_data.name].handle_new_data(host_data);
  },
  flush_data: function(cb){
    fs.writeFile(this._flush_path, JSON.stringify(this._projects), function(err){
      if (err){
        console.log(err);
      }
      if (cb){
        cb();
      }
    });
  },
  get_service_stats: function(project){
    return this._projects[project];
  },
  get_unhealth_services: function(project){
    var services = this._projects[project];
    var unhealthy = [];
    _.each(services, function(service, service_name){
      if (service.health !== HEALTH_OK){
        unhealthy.push(service);
      }
    });
    return unhealthy;
  },
  // internal representation of all projects' services' stats (this is static)
  // _projects[project_name][host_name] = api_host_properties
  _projects: {}
});

/**
 * Takes a username and password and returns a basic auth token fully formed
 *
 */
exports.create_basic_auth = function(username, password){
  var auth_token = new Buffer(username + ':' + password).toString('base64');
  return "Basic " + auth_token;
};

/**
 * Takes middlware function pointer with arrity of 5 and returns a middleware
 *
 * @param {string} name the name of the middleware (should be unique)
 * @param {fn} the middleware proper which should accept (req, res, next, payload, api_config- set payload.data/errors to cache stuff, api_config = req.devops[name]
 * @return {fn} middleware function
 */
exports.create_middleware = function(name, middleware){
  return function(req, res, next){
    // Do they have the api defined in the devops ball?
    if (!req.devops.related_apis || !req.devops.related_apis[name]) {
      req.devops[name] = null;
      next();
      return;
    }
      var payload = {error: null, data: null};
      req.devops[name] = payload;
      try{
        middleware(req, res, next, payload, req.devops.related_apis[name]);
      }catch (e){
        payload.error = e;
        // TODO: this may not be callable if the error was with express
        next();
      }
  };
};

/**
 * Makes a fake response object which the injector middleware needs (as for unit tests).  Call with new!
 *
 * @return {obj}
 */
 var mock_res = function(){};
 mock_res.prototype = {
  locals: function(obj){
    if (!this._locals){
      this._locals = {};
    }
    if (!obj){
      return this._locals;
    }
    for (var key in obj){
      var val = obj[key];
      if (val===undefined){
        continue;
      }
      this._locals[key] = val;
    }
  }
};
exports.mock_res = mock_res;
/**
 * Takes a relative or absolute devops filename and returns the parsed content
 *
 * @param {string} devops_filename the filename of the devopsjson file relative to the fixtures directory
 * @return {Object} parsed content of JSON file
 */
exports.load_devops = function(devops_path) {
  var devops;
  devops = fs.readFileSync(devops_path);
  devops = JSON.parse(devops);
  return devops;
};

/** A convenience function for making requests without having to build up response body data.
 * Also useful for mocking the making of requests in tests
 * @param {Object} options an options dict such as http.request would use
 * @param {fn} on_success callback that takes the complete response body data as a parameter
 * @param {fn} on_error callback that takes an Exception as a parameter
 */
exports.request_maker = function(options, call_back) {
  var method = options.port === 443 ? https : http;
  var req = method.get(options, function(res) {
    var data = '';
    res.setEncoding('utf8');
    res.on('data', function(d) {
      data += d;
    });
    res.on('end', function() {
      //TODO:  this is pretty strange, change me.
      try{
        call_back(null, data);
      }catch(e){
        call_back(e, data);
      }
    });
  }).on('error', function(e) {
    call_back(e, null);
  });
};

/**
 * @param string
 * @returns new string with first letter capitalized
 */
exports.capitalize = function(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
};

exports.validate_devops = function(devops){
  var jsv_env = JSV.createEnvironment('json-schema-draft-03');
  var report = jsv_env.validate(devops, schema);

  if (report.errors.length > 0) {
    console.log("ERRORS: ", report.errors);
  }
  return report;
};

/*
 * Javascript Humane Dates
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 *
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 *
 * Licensed under the MIT license.
 */

exports.humane_date = function(date, compareTo){

  if(!date) {
      return;
  }

  var lang = {
    ago: 'Ago',
    from: '',
    now: 'Just Now',
    minute: 'Minute',
    minutes: 'Minutes',
    hour: 'Hour',
    hours: 'Hours',
    day: 'Day',
    days: 'Days',
    week: 'Week',
    weeks: 'Weeks',
    month: 'Month',
    months: 'Months',
    year: 'Year',
    years: 'Years'
  },
  formats = [
    [60, lang.now],
    [3600, lang.minute, lang.minutes, 60], // 60 minutes, 1 minute
    [86400, lang.hour, lang.hours, 3600], // 24 hours, 1 hour
    [604800, lang.day, lang.days, 86400], // 7 days, 1 day
    [2628000, lang.week, lang.weeks, 604800], // ~1 month, 1 week
    [31536000, lang.month, lang.months, 2628000], // 1 year, ~1 month
    [Infinity, lang.year, lang.years, 31536000] // Infinity, 1 year
  ],
  isString = typeof date === 'string',
  date = isString ?
    new Date(('' + date).replace(/-/g,"/").replace(/[TZ]/g," ")) :
    date,
  compareTo = compareTo || new Date(),
  seconds = (compareTo - date + (
      compareTo.getTimezoneOffset() -
      // if we received a GMT time from a string, doesn't include time zone bias
      // if we got a date object, the time zone is built in, we need to remove it.
      (isString ? 0 : date.getTimezoneOffset())
    ) * 60000 ) / 1000,
  token;

  if(seconds < 0) {
    seconds = Math.abs(seconds);
    token = lang.from ? ' ' + lang.from : '';
  } else {
    token = lang.ago ? ' ' + lang.ago : '';
  }

  /*
   * 0 seconds && < 60 seconds        Now
   * 60 seconds                       1 Minute
   * > 60 seconds && < 60 minutes     X Minutes
   * 60 minutes                       1 Hour
   * > 60 minutes && < 24 hours       X Hours
   * 24 hours                         1 Day
   * > 24 hours && < 7 days           X Days
   * 7 days                           1 Week
   * > 7 days && < ~ 1 Month          X Weeks
   * ~ 1 Month                        1 Month
   * > ~ 1 Month && < 1 Year          X Months
   * 1 Year                           1 Year
   * > 1 Year                         X Years
   *
   * Single units are +10%. 1 Year shows first at 1 Year + 10%
   */

  function normalize(val, single)
  {
    var margin = 0.1;
    if(val >= single && val <= single * (1+margin)) {
      return single;
    }
    return val;
  }

  for(var i = 0, format = formats[0]; formats[i]; format = formats[++i]) {
    if(seconds < format[0]) {
      if(i === 0) {
        // Now
        return format[1];
      }

      var val = Math.ceil(normalize(seconds, format[3]) / (format[3]));
      return val + ' ' + (val !== 1 ? format[2] : format[1]) + (i > 0 ? token : '');
    }
  }
};
<commit_msg>Set service health metrics. Lint.<commit_after>var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var urls = require('./web/urls');
var settings = require('./settings');
var schema = require('../extern/devopsjson/lib/web/schema').schema;
var JSV = require('JSV').JSV;
var _ = require('underscore');

// A bunch of enum like things for the stats push API (no hidden interfaces via magic strings!)
var HEALTH_UNKNOWN = 0;
var HEALTH_OK = 1;
var HEALTH_WARNING = 2;
var HEALTH_ERROR = 3;

// this sucks
exports.HEALTH_ERROR = HEALTH_ERROR;

var HEALTH_STRING_OK = 'OK';
var HEALTH_STRING_PROBLEM = 'PROBLEM';
var HEALTH_STRING_ERROR = 'ERROR';
var HEALTH_STRING_UNKNOWN = 'UNKNOWN';

exports.HEALTH_STRING_OK = HEALTH_STRING_OK;
exports.HEALTH_STRING_PROBLEM = HEALTH_STRING_PROBLEM;
exports.HEALTH_STRING_ERROR = HEALTH_STRING_ERROR;
exports.HEALTH_STRING_UNKNOWN = HEALTH_STRING_UNKNOWN;

exports.HEALTH_ENUM = {};
exports.HEALTH_ENUM[HEALTH_OK] = HEALTH_STRING_OK;
exports.HEALTH_ENUM[HEALTH_WARNING] = HEALTH_STRING_PROBLEM;
exports.HEALTH_ENUM[HEALTH_ERROR] = HEALTH_STRING_ERROR;
exports.HEALTH_ENUM[HEALTH_UNKNOWN] = HEALTH_STRING_UNKNOWN;

// build an array (of Numbers) to check against
var VALID_HEALTH = _.map(_.keys(exports.HEALTH_ENUM), function(n){return Number(n);});
exports.VALID_HEALTH = VALID_HEALTH;

/**
 * makeClass - By John Resig (MIT Licensed)
 * Takes a function and ensures that new is called so that __this__ is properly bound
 * @param {proto} optional prototype to set on the returned function
 */
exports.make_class = function(proto){
  var f = function(args){
    // did you make me with new?
    if (this instanceof arguments.callee){
      // am I a function?
      if (typeof this.init === "function"){
        //PREGUNTA: why not always pass apply arguments?
        if (args){
          return this.init.apply(this, args.callee ? args : arguments );
        }
        else{
          return this.init.apply(this, arguments);
        }
      }
    } else{
      // didn't use new, return a properly instantiated object
      return new arguments.callee(arguments);
    }
  };
  if (proto){
    f.prototype = proto;
  }
  return f;
};

/** A representation of the stats for a given host: see https://github.com/racker/gutsy/blob/master/docs/example_data_push.js
 * for the format of data
 */
var api_host_properties = exports.make_class({
  /** Constructor which takes a data blob
  * @param {object} data stats for a given host (see link above)
 */
  init: function(data){
    this.validate_data(data);
    _.extend(this, data);
  },
  /** Takes new data, validates it, and updates the local store of any fresh data
  * @param {object} data stats for a given host (see link above)
 */
  handle_new_data: function(data){
    var fresh, that;
    this.validate_data(data);

    fresh = false;
    if (data.timestamp >= this.timestamp){
      fresh = true;
      this.timestamp = data.timestamp;
      this.health = data.health;
      this.ip = data.ip;
      this.metrics = data.metrics;
    }

    that = this;
    _.each(data.services, function(service, service_name){
      var should_update_service = (!that.services[service_name]) ||
        (that.services[service_name].timestamp < service.timestamp);
      if (should_update_service){
        that.services[service_name] = service;
      }
    });
  },
  validate_data: function(data){
    var that = this;
    //TODO: use swiz for this
    if (!data.timestamp){
      throw new Error('You must specify a timestamp');
    }
    if (!data.name){
      throw new Error('You must specify a name');
    }
    data.timestamp = this._validate_time(data.timestamp);
    data.health = this._validate_health(data.health);
    _.each(data.services, function(service, service_name){
      service.timestamp = data.timestamp;
      service.health = that._validate_health(service.health);
    });
  },
  _validate_time: function(time_like){
    // have Date parse the time like thing and convert to a Number or bail
    return (new Date(time_like)).getTime();
  },
  _validate_health: function(health){
    if (VALID_HEALTH.indexOf(health) !== -1){
      return health;
    }
    throw new Error('Health status: ' + health + ' is not valid');
  }
});

/** A representation of the stats for all hosts.  This data is flushed to disk every settings.flush_stats_interval and
 * reloaded from disk at startup
 */
exports.api_cache = exports.make_class({
  init: function(){
    var that = this;
    var data = null;
    var flush_file;
    if(settings.testing){
      flush_file = settings.testing_flush_file_path;
    }else{
      flush_file = settings.flush_file_path;
    }
    this._flush_path = path.join(__dirname, flush_file);
    // try loading flushed data at boot
    try{
      data = fs.readFileSync(this._flush_path);
    } catch(e){
      // if this is the first run, the file may not exist (this is unsafe)
      if (e.code !== 'ENOENT'){
        console.warn('\nCouldn\'t load flushed data!\n');
        throw e;
      }
    }
    if (data !== null && data.length > 0){
      // if we have data, parse it and push into our cache
      data = JSON.parse(data);
      _.each(data, function(project, project_name){
        _.each(project, function(service){
          that.handle_push(project_name, service);
        });
      });
    }
  },
  /** Handles any new data for a given project.
  * @param {string} project the name of the project these stats correspond to
  * @param {object} host_data the given data for a host (see link in api_host_properties docs)
   */
  handle_push: function(project, host_data){
    if (!this._projects[project]){
      this._projects[project] = {};
    }
    if (!this._projects[project][host_data.name]){
      this._projects[project][host_data.name] = new api_host_properties(host_data);
    }
    this._projects[project][host_data.name].handle_new_data(host_data);
  },
  flush_data: function(cb){
    fs.writeFile(this._flush_path, JSON.stringify(this._projects), function(err){
      if (err){
        console.log(err);
      }
      if (cb){
        cb();
      }
    });
  },
  get_service_stats: function(project){
    return this._projects[project];
  },
  get_unhealth_services: function(project){
    var services = this._projects[project];
    var unhealthy = [];
    _.each(services, function(service, service_name){
      if (service.health !== HEALTH_OK){
        unhealthy.push(service);
      }
    });
    return unhealthy;
  },
  // internal representation of all projects' services' stats (this is static)
  // _projects[project_name][host_name] = api_host_properties
  _projects: {}
});

/**
 * Takes a username and password and returns a basic auth token fully formed
 *
 */
exports.create_basic_auth = function(username, password){
  var auth_token = new Buffer(username + ':' + password).toString('base64');
  return "Basic " + auth_token;
};

/**
 * Takes middlware function pointer with arrity of 5 and returns a middleware
 *
 * @param {string} name the name of the middleware (should be unique)
 * @param {fn} the middleware proper which should accept (req, res, next, payload, api_config- set payload.data/errors to cache stuff, api_config = req.devops[name]
 * @return {fn} middleware function
 */
exports.create_middleware = function(name, middleware){
  return function(req, res, next){
    // Do they have the api defined in the devops ball?
    if (!req.devops.related_apis || !req.devops.related_apis[name]) {
      req.devops[name] = null;
      next();
      return;
    }
      var payload = {error: null, data: null};
      req.devops[name] = payload;
      try{
        middleware(req, res, next, payload, req.devops.related_apis[name]);
      }catch (e){
        payload.error = e;
        // TODO: this may not be callable if the error was with express
        next();
      }
  };
};

/**
 * Makes a fake response object which the injector middleware needs (as for unit tests).  Call with new!
 *
 * @return {obj}
 */
 var mock_res = function(){};
 mock_res.prototype = {
  locals: function(obj){
    if (!this._locals){
      this._locals = {};
    }
    if (!obj){
      return this._locals;
    }
    for (var key in obj){
      var val = obj[key];
      if (val===undefined){
        continue;
      }
      this._locals[key] = val;
    }
  }
};
exports.mock_res = mock_res;
/**
 * Takes a relative or absolute devops filename and returns the parsed content
 *
 * @param {string} devops_filename the filename of the devopsjson file relative to the fixtures directory
 * @return {Object} parsed content of JSON file
 */
exports.load_devops = function(devops_path) {
  var devops;
  devops = fs.readFileSync(devops_path);
  devops = JSON.parse(devops);
  return devops;
};

/** A convenience function for making requests without having to build up response body data.
 * Also useful for mocking the making of requests in tests
 * @param {Object} options an options dict such as http.request would use
 * @param {fn} on_success callback that takes the complete response body data as a parameter
 * @param {fn} on_error callback that takes an Exception as a parameter
 */
exports.request_maker = function(options, call_back) {
  var method = options.port === 443 ? https : http;
  var req = method.get(options, function(res) {
    var data = '';
    res.setEncoding('utf8');
    res.on('data', function(d) {
      data += d;
    });
    res.on('end', function() {
      //TODO:  this is pretty strange, change me.
      try{
        call_back(null, data);
      }catch(e){
        call_back(e, data);
      }
    });
  }).on('error', function(e) {
    call_back(e, null);
  });
};

/**
 * @param string
 * @returns new string with first letter capitalized
 */
exports.capitalize = function(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
};

exports.validate_devops = function(devops){
  var jsv_env = JSV.createEnvironment('json-schema-draft-03');
  var report = jsv_env.validate(devops, schema);

  if (report.errors.length > 0) {
    console.log("ERRORS: ", report.errors);
  }
  return report;
};

/*
 * Javascript Humane Dates
 * Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 * Re-write by Zach Leatherman (zachleat.com)
 *
 * Adopted from the John Resig's pretty.js
 * at http://ejohn.org/blog/javascript-pretty-date
 * and henrah's proposed modification
 * at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 *
 * Licensed under the MIT license.
 */

exports.humane_date = function(date, compareTo){

  if(!date) {
      return;
  }

  date = isString ?
    new Date(('' + date).replace(/-/g,"/").replace(/[TZ]/g," ")) :
    date;
  compareTo = compareTo || new Date();

  var lang = {
    ago: 'Ago',
    from: '',
    now: 'Just Now',
    minute: 'Minute',
    minutes: 'Minutes',
    hour: 'Hour',
    hours: 'Hours',
    day: 'Day',
    days: 'Days',
    week: 'Week',
    weeks: 'Weeks',
    month: 'Month',
    months: 'Months',
    year: 'Year',
    years: 'Years'
  },
  formats = [
    [60, lang.now],
    [3600, lang.minute, lang.minutes, 60], // 60 minutes, 1 minute
    [86400, lang.hour, lang.hours, 3600], // 24 hours, 1 hour
    [604800, lang.day, lang.days, 86400], // 7 days, 1 day
    [2628000, lang.week, lang.weeks, 604800], // ~1 month, 1 week
    [31536000, lang.month, lang.months, 2628000], // 1 year, ~1 month
    [Infinity, lang.year, lang.years, 31536000] // Infinity, 1 year
  ],
  isString = typeof date === 'string',
  seconds = (compareTo - date + (
      compareTo.getTimezoneOffset() -
      // if we received a GMT time from a string, doesn't include time zone bias
      // if we got a date object, the time zone is built in, we need to remove it.
      (isString ? 0 : date.getTimezoneOffset())
    ) * 60000 ) / 1000,
  token;

  if(seconds < 0) {
    seconds = Math.abs(seconds);
    token = lang.from ? ' ' + lang.from : '';
  } else {
    token = lang.ago ? ' ' + lang.ago : '';
  }

  /*
   * 0 seconds && < 60 seconds        Now
   * 60 seconds                       1 Minute
   * > 60 seconds && < 60 minutes     X Minutes
   * 60 minutes                       1 Hour
   * > 60 minutes && < 24 hours       X Hours
   * 24 hours                         1 Day
   * > 24 hours && < 7 days           X Days
   * 7 days                           1 Week
   * > 7 days && < ~ 1 Month          X Weeks
   * ~ 1 Month                        1 Month
   * > ~ 1 Month && < 1 Year          X Months
   * 1 Year                           1 Year
   * > 1 Year                         X Years
   *
   * Single units are +10%. 1 Year shows first at 1 Year + 10%
   */

  function normalize(val, single)
  {
    var margin = 0.1;
    if(val >= single && val <= single * (1+margin)) {
      return single;
    }
    return val;
  }

  for(var i = 0, format = formats[0]; formats[i]; format = formats[++i]) {
    if(seconds < format[0]) {
      if(i === 0) {
        // Now
        return format[1];
      }

      var val = Math.ceil(normalize(seconds, format[3]) / (format[3]));
      return val + ' ' + (val !== 1 ? format[2] : format[1]) + (i > 0 ? token : '');
    }
  }
};
<|endoftext|>


<commit_before>export let styles = {
  item: {
    padding: '2px 6px',
    cursor: 'default'
  },

  highlightedItem: {
    color: 'white',
    background: 'hsl(200, 50%, 50%)',
    padding: '2px 6px',
    cursor: 'default'
  },

  menu: {
    border: 'solid 1px #ccc'
  }
}

export function matchStateToTerm (state, value) {
  return (
    state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 ||
    state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1
  )
}

export function sortStates (a, b, value) {
  return (
    a.name.toLowerCase().indexOf(value.toLowerCase()) >
    b.name.toLowerCase().indexOf(value.toLowerCase()) ? 1 : -1
  )
}

export function fakeRequest (value, cb) {
  if (value === '')
    return getStates()
  var items = getStates().filter((state) => {
    return matchStateToTerm(state, value)
  })
  setTimeout(() => {
    cb(items)
  }, 500)
}

export function getStates() {
  return [
    { abbr: "AL", name: "Alabama"},
    { abbr: "AK", name: "Alaska"},
    { abbr: "AZ", name: "Arizona"},
    { abbr: "AR", name: "Arkansas"},
    { abbr: "CA", name: "California"},
    { abbr: "CO", name: "Colorado"},
    { abbr: "CT", name: "Connecticut"},
    { abbr: "DE", name: "Delaware"},
    { abbr: "FL", name: "Florida"},
    { abbr: "GA", name: "Georgia"},
    { abbr: "HI", name: "Hawaii"},
    { abbr: "ID", name: "Idaho"},
    { abbr: "IL", name: "Illinois"},
    { abbr: "IN", name: "Indiana"},
    { abbr: "IA", name: "Iowa"},
    { abbr: "KS", name: "Kansas"},
    { abbr: "KY", name: "Kentucky"},
    { abbr: "LA", name: "Louisiana"},
    { abbr: "ME", name: "Maine"},
    { abbr: "MD", name: "Maryland"},
    { abbr: "MA", name: "Massachusetts"},
    { abbr: "MI", name: "Michigan"},
    { abbr: "MN", name: "Minnesota"},
    { abbr: "MS", name: "Mississippi"},
    { abbr: "MO", name: "Missouri"},
    { abbr: "MT", name: "Montana"},
    { abbr: "NE", name: "Nebraska"},
    { abbr: "NV", name: "Nevada"},
    { abbr: "NH", name: "New Hampshire"},
    { abbr: "NJ", name: "New Jersey"},
    { abbr: "NM", name: "New Mexico"},
    { abbr: "NY", name: "New York"},
    { abbr: "NC", name: "North Carolina"},
    { abbr: "ND", name: "North Dakota"},
    { abbr: "OH", name: "Ohio"},
    { abbr: "OK", name: "Oklahoma"},
    { abbr: "OR", name: "Oregon"},
    { abbr: "PA", name: "Pennsylvania"},
    { abbr: "RI", name: "Rhode Island"},
    { abbr: "SC", name: "South Carolina"},
    { abbr: "SD", name: "South Dakota"},
    { abbr: "TN", name: "Tennessee"},
    { abbr: "TX", name: "Texas"},
    { abbr: "UT", name: "Utah"},
    { abbr: "VT", name: "Vermont"},
    { abbr: "VA", name: "Virginia"},
    { abbr: "WA", name: "Washington"},
    { abbr: "WV", name: "West Virginia"},
    { abbr: "WI", name: "Wisconsin"},
    { abbr: "WY", name: "Wyoming"}
  ]
}

<commit_msg>Nicer sorting algorithm for example<commit_after>export let styles = {
  item: {
    padding: '2px 6px',
    cursor: 'default'
  },

  highlightedItem: {
    color: 'white',
    background: 'hsl(200, 50%, 50%)',
    padding: '2px 6px',
    cursor: 'default'
  },

  menu: {
    border: 'solid 1px #ccc'
  }
}

export function matchStateToTerm (state, value) {
  return (
    state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 ||
    state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1
  )
}

/**
 * An example of how to implement a relevancy-based sorting method. States are
 * sorted based on the location of the match - For example, a search for "or"
 * will return "Oregon" before "North Carolina" even though "North Carolina"
 * would normally sort above Oregon. Strings where the match is in the same
 * location (or there is no match) will be sorted alphabetically - For example,
 * a search for "or" would return "North Carolina" above "North Dakota".
 */
export function sortStates (a, b, value) {
  const aLower = a.name.toLowerCase();
  const bLower = b.name.toLowerCase();
  const valueLower = value.toLowerCase();
  const queryPosA = aLower.indexOf(valueLower);
  const queryPosB = bLower.indexOf(valueLower);
  if (queryPosA !== queryPosB) {
    return queryPosA - queryPosB;
  }
  return aLower < bLower ? -1 : 1;
}

export function fakeRequest (value, cb) {
  if (value === '')
    return getStates()
  var items = getStates().filter((state) => {
    return matchStateToTerm(state, value)
  })
  setTimeout(() => {
    cb(items)
  }, 500)
}

export function getStates() {
  return [
    { abbr: "AL", name: "Alabama"},
    { abbr: "AK", name: "Alaska"},
    { abbr: "AZ", name: "Arizona"},
    { abbr: "AR", name: "Arkansas"},
    { abbr: "CA", name: "California"},
    { abbr: "CO", name: "Colorado"},
    { abbr: "CT", name: "Connecticut"},
    { abbr: "DE", name: "Delaware"},
    { abbr: "FL", name: "Florida"},
    { abbr: "GA", name: "Georgia"},
    { abbr: "HI", name: "Hawaii"},
    { abbr: "ID", name: "Idaho"},
    { abbr: "IL", name: "Illinois"},
    { abbr: "IN", name: "Indiana"},
    { abbr: "IA", name: "Iowa"},
    { abbr: "KS", name: "Kansas"},
    { abbr: "KY", name: "Kentucky"},
    { abbr: "LA", name: "Louisiana"},
    { abbr: "ME", name: "Maine"},
    { abbr: "MD", name: "Maryland"},
    { abbr: "MA", name: "Massachusetts"},
    { abbr: "MI", name: "Michigan"},
    { abbr: "MN", name: "Minnesota"},
    { abbr: "MS", name: "Mississippi"},
    { abbr: "MO", name: "Missouri"},
    { abbr: "MT", name: "Montana"},
    { abbr: "NE", name: "Nebraska"},
    { abbr: "NV", name: "Nevada"},
    { abbr: "NH", name: "New Hampshire"},
    { abbr: "NJ", name: "New Jersey"},
    { abbr: "NM", name: "New Mexico"},
    { abbr: "NY", name: "New York"},
    { abbr: "NC", name: "North Carolina"},
    { abbr: "ND", name: "North Dakota"},
    { abbr: "OH", name: "Ohio"},
    { abbr: "OK", name: "Oklahoma"},
    { abbr: "OR", name: "Oregon"},
    { abbr: "PA", name: "Pennsylvania"},
    { abbr: "RI", name: "Rhode Island"},
    { abbr: "SC", name: "South Carolina"},
    { abbr: "SD", name: "South Dakota"},
    { abbr: "TN", name: "Tennessee"},
    { abbr: "TX", name: "Texas"},
    { abbr: "UT", name: "Utah"},
    { abbr: "VT", name: "Vermont"},
    { abbr: "VA", name: "Virginia"},
    { abbr: "WA", name: "Washington"},
    { abbr: "WV", name: "West Virginia"},
    { abbr: "WI", name: "Wisconsin"},
    { abbr: "WY", name: "Wyoming"}
  ]
}

<|endoftext|>


<commit_before>const parser = require('./parser');
const packer = require('./packer');

const types = {
	FN_CALL: '0x1',
	CALLBACK_CALL: '0x2',
	END: '0x3',
	AUTH: '0x4',
	META: '0x5',
	ERROR: '0x6'
};

function camelCase(input) {
	return input.toLowerCase().replace(/_(.)/g, function(match, group1) {
		return group1.toUpperCase();
	});
}

module.exports.actionType = function (data, obj) {
	var type = data.type;
	for (var i in types) {
		if (type === types[i] && obj[types[i]]) {
			obj[types[i]](data.data);
			return;
		}
	}
};

module.exports.processApiCall = function (target, method, args, callbacks) {
	if (!target[method] && typeof target[method] !== 'function') {
		return false;
	}
	target[method].apply(undefined, args);
	return true;
};

module.exports.wrapInType = function (type, data) {
	return {
		type: type,
		data: data
	};
};

module.exports.writeToSock = function (socket, data) {
	socket.write(packer.pack(data));
	socket.write(parser.terminator);
};

module.exports.onData = function (obj, conn, prop) {
	return parser.bind(undefined, conn, obj[prop].bind(obj));
};

module.exports.createRemoteProxy = function (fn, context) {
	return Proxy.create({
		get: fn.bind(context)
	});
};

module.exports.argsToArray = function (args) {
	return Array.prototype.slice.call(args);
};

for (var i in types) {
	var str = i.toLowerCase();
	str = camelCase(str);
	str = str.charAt(0).toUpperCase() + str.slice(1);
	module.exports['wrapIn' + str + 'Type'] = module.exports.wrapInType.bind(undefined, types[i]);
}

module.exports.types = types;
<commit_msg>Made it 20% faster<commit_after>const parser = require('./parser');
const packer = require('./packer');

const types = {
	FN_CALL: '0x1',
	CALLBACK_CALL: '0x2',
	END: '0x3',
	AUTH: '0x4',
	META: '0x5',
	ERROR: '0x6'
};

function camelCase(input) {
	return input.toLowerCase().replace(/_(.)/g, function(match, group1) {
		return group1.toUpperCase();
	});
}

module.exports.actionType = function (data, obj) {
	var type = data.type;
	for (var i in types) {
		if (type === types[i] && obj[types[i]]) {
			obj[types[i]](data.data);
			return;
		}
	}
};

module.exports.processApiCall = function (target, method, args, callbacks) {
	if (!target[method] && typeof target[method] !== 'function') {
		return false;
	}
	target[method].apply(undefined, args);
	return true;
};

module.exports.wrapInType = function (type, data) {
	return {
		type: type,
		data: data
	};
};

module.exports.writeToSock = function (socket, data) {
	socket.write(packer.pack(data));
	socket.write(parser.terminator);
};

module.exports.onData = function (obj, conn, prop) {
	return setImmediate.bind(undefined, parser.bind(undefined, conn, obj[prop].bind(obj)));
};

module.exports.createRemoteProxy = function (fn, context) {
	return Proxy.create({
		get: fn.bind(context)
	});
};

module.exports.argsToArray = function (args) {
	return Array.prototype.slice.call(args);
};

for (var i in types) {
	var str = i.toLowerCase();
	str = camelCase(str);
	str = str.charAt(0).toUpperCase() + str.slice(1);
	module.exports['wrapIn' + str + 'Type'] = module.exports.wrapInType.bind(undefined, types[i]);
}

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


<commit_before>// Core dependencies
const fs = require('fs')

// NPM dependencies
const basicAuth = require('basic-auth')
const marked = require('marked')
const path = require('path')
const portScanner = require('portscanner')
const prompt = require('prompt')
const request = require('sync-request')

// Local dependencies
const config = require('../app/config.js')

// Variables
var releaseUrl = null

// Require core and custom filters, merges to one object
// and then add the methods to Nunjucks environment
exports.addNunjucksFilters = function (env) {
  var coreFilters = require('./core_filters.js')(env)
  var customFilters = require('../app/filters.js')(env)
  var filters = Object.assign(coreFilters, customFilters)
  Object.keys(filters).forEach(function (filterName) {
    env.addFilter(filterName, filters[filterName])
  })
}

// Add Nunjucks function called 'checked' to populate radios and checkboxes
exports.addCheckedFunction = function (env) {
  env.addGlobal('checked', function (name, value) {
    // Check data exists
    if (this.ctx.data === undefined) {
      return ''
    }

    var storedValue = this.ctx.data[name]

    // Check the requested data exists
    if (storedValue === undefined) {
      return ''
    }

    var checked = ''

    // If data is an array, check it exists in the array
    if (Array.isArray(storedValue)) {
      if (storedValue.indexOf(value) !== -1) {
        checked = 'checked'
      }
    } else {
      // The data is just a simple value, check it matches
      if (storedValue === value) {
        checked = 'checked'
      }
    }
    return checked
  })
}

/**
 * Simple basic auth middleware for use with Express 4.x.
 *
 * Based on template found at: http://www.danielstjules.com/2014/08/03/basic-auth-with-express-4/
 *
 * @example
 * app.use('/api-requiring-auth', utils.basicAuth('username', 'password'))
 *
 * @param   {string}   username Expected username
 * @param   {string}   password Expected password
 * @returns {function} Express 4 middleware requiring the given credentials
 */

exports.basicAuth = function (username, password) {
  return function (req, res, next) {
    if (!username || !password) {
      console.log('Username or password is not set.')
      return res.send('
Error:

Username or password not set. See guidance for setting these.
')
    }

    var user = basicAuth(req)

    if (!user || user.name !== username || user.pass !== password) {
      res.set('WWW-Authenticate', 'Basic realm=Authorization Required')
      return res.sendStatus(401)
    }

    next()
  }
}

// Find an available port to run the server on
exports.findAvailablePort = function (app, callback) {
  var port = null

  // When the server starts, we store the port in .port.tmp so it tries to restart
  // on the same port
  try {
    port = Number(fs.readFileSync(path.join(__dirname, '/../.port.tmp')))
  } catch (e) {
    port = Number(process.env.PORT || config.port)
  }

  console.log('')

  // Check port is free, else offer to change
  portScanner.findAPortNotInUse(port, port + 50, '127.0.0.1', function (error, availablePort) {
    if (error) { throw error }
    if (port === availablePort) {
      // Port is free, return it via the callback
      callback(port)
    } else {
      // Port in use - offer to change to available port
      console.error('ERROR: Port ' + port + ' in use - you may have another prototype running.\n')
      // Set up prompt settings
      prompt.colors = false
      prompt.start()
      prompt.message = ''
      prompt.delimiter = ''

      // Ask user if they want to change port
      prompt.get([{
        name: 'answer',
        description: 'Change to an available port? (y/n)',
        required: true,
        type: 'string',
        pattern: /y(es)?|no?/i,
        message: 'Please enter y or n'
      }], function (err, result) {
        if (err) { throw err }
        if (result.answer.match(/y(es)?/i)) {
          // User answers yes
          port = availablePort
          fs.writeFileSync(path.join(__dirname, '/../.port.tmp'), port)
          console.log('Changed to port ' + port)

          callback(port)
        } else {
          // User answers no - exit
          console.log('\nYou can set a new default port in server.js, or by running the server with PORT=XXXX')
          console.log("\nExit by pressing 'ctrl + c'")
          process.exit(0)
        }
      })
    }
  })
}

// Redirect HTTP requests to HTTPS
exports.forceHttps = function (req, res, next) {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    console.log('Redirecting request to https')
    // 302 temporary - this is a feature that can be disabled
    return res.redirect(302, 'https://' + req.get('Host') + req.url)
  }
  next()
}

// Synchronously get the URL for the latest release on GitHub and cache it
exports.getLatestRelease = function () {
  var url = ''

  if (releaseUrl !== null) {
    // Release URL already exists
    console.log('Release url cached:', releaseUrl)
    return releaseUrl
  } else {
    // Release URL doesn't exist
    var options = {
      headers: {'user-agent': 'node.js'}
    }
    var gitHubUrl = 'https://api.github.com/repos/alphagov/govuk-prototype-kit/releases/latest'
    try {
      console.log('Getting latest release from GitHub')

      var res = request('GET', gitHubUrl, options)
      var data = JSON.parse(res.getBody('utf8'))
      var zipballUrl = data['zipball_url']
      var releaseVersion = zipballUrl.split('/').pop()
      var urlStart = 'https://github.com/alphagov/govuk-prototype-kit/archive/'
      var urlEnd = '.zip'
      var zipUrl = urlStart + releaseVersion + urlEnd

      console.log('Release URL is', zipUrl)
      releaseUrl = zipUrl
      url = releaseUrl
    } catch (err) {
      url = 'https://github.com/alphagov/govuk-prototype-kit/releases/latest'
      console.log("Couldn't retrieve release URL")
    }
  }
  return url
}

// Try to match a request to a template, for example a request for /test
// would look for /app/views/test.html
// or /app/views/text/index.html
exports.matchRoutes = function (req, res) {
  var path = (req.params[0])
  res.render(path, function (err, html) {
    if (err) {
      res.render(path + '/index', function (err2, html) {
        if (err2) {
          res.status(404).send(err + '
' + err2)
        } else {
          res.end(html)
        }
      })
    } else {
      res.end(html)
    }
  })
}

// Try to match a request to a Markdown file and render it
exports.matchMdRoutes = function (req, res) {
  var docsPath = '/../docs/documentation/'
  if (fs.existsSync(path.join(__dirname, docsPath, req.params[0] + '.md'), 'utf8')) {
    var doc = fs.readFileSync(path.join(__dirname, docsPath, req.params[0] + '.md'), 'utf8')
    var html = marked(doc)
    res.render('documentation_template', {'document': html})
    return true
  }
  return false
}

// Store data from POST body or GET query in session
var storeData = function (input, store) {
  for (var i in input) {
    // any input where the name starts with _ is ignored
    if (i.indexOf('_') === 0) {
      continue
    }

    var val = input[i]

    // Delete values when users unselect checkboxes
    if (val === '_unchecked' || val === ['_unchecked']) {
      delete store.data[i]
      continue
    }

    // Remove _unchecked from arrays of checkboxes
    if (Array.isArray(val)) {
      var index = val.indexOf('_unchecked')
      if (index !== -1) {
        val.splice(index, 1)
      }
    }

    store.data[i] = val
  }
}

// Get session default data from file
let sessionDataDefaults = {}

const sessionDataDefaultsFile = path.join(__dirname, '/../app/data/session-data-defaults.js')

try {
  sessionDataDefaults = require(sessionDataDefaultsFile)
} catch (e) {
  console.error('Could not load the session data defaults from app/data/session-data-defaults.js. Might be a syntax error?')
}

// Middleware - store any data sent in session, and pass it to all views
exports.autoStoreData = function (req, res, next) {
  if (!req.session.data) {
    req.session.data = {}
  }

  req.session.data = Object.assign({}, sessionDataDefaults, req.session.data)

  storeData(req.body, req.session)
  storeData(req.query, req.session)

  // Send session data to all views

  res.locals.data = {}

  for (var j in req.session.data) {
    res.locals.data[j] = req.session.data[j]
  }

  next()
}

exports.handleCookies = function (app) {
  return function handleCookies (req, res, next) {
    const COOKIE_NAME = 'seen_cookie_message'
    let cookie = req.cookies[COOKIE_NAME]

    if (cookie === 'yes') {
      app.locals.shouldShowCookieMessage = false
      return next()
    }

    let maxAgeInDays = 28
    res.cookie(COOKIE_NAME, 'yes', {
      maxAge: maxAgeInDays * 24 * 60 * 60 * 1000,
      httpOnly: true
    })

    app.locals.shouldShowCookieMessage = true

    next()
  }
}
<commit_msg>Update link to 'set username and password' in docs<commit_after>// Core dependencies
const fs = require('fs')

// NPM dependencies
const basicAuth = require('basic-auth')
const marked = require('marked')
const path = require('path')
const portScanner = require('portscanner')
const prompt = require('prompt')
const request = require('sync-request')

// Local dependencies
const config = require('../app/config.js')

// Variables
var releaseUrl = null

// Require core and custom filters, merges to one object
// and then add the methods to Nunjucks environment
exports.addNunjucksFilters = function (env) {
  var coreFilters = require('./core_filters.js')(env)
  var customFilters = require('../app/filters.js')(env)
  var filters = Object.assign(coreFilters, customFilters)
  Object.keys(filters).forEach(function (filterName) {
    env.addFilter(filterName, filters[filterName])
  })
}

// Add Nunjucks function called 'checked' to populate radios and checkboxes
exports.addCheckedFunction = function (env) {
  env.addGlobal('checked', function (name, value) {
    // Check data exists
    if (this.ctx.data === undefined) {
      return ''
    }

    var storedValue = this.ctx.data[name]

    // Check the requested data exists
    if (storedValue === undefined) {
      return ''
    }

    var checked = ''

    // If data is an array, check it exists in the array
    if (Array.isArray(storedValue)) {
      if (storedValue.indexOf(value) !== -1) {
        checked = 'checked'
      }
    } else {
      // The data is just a simple value, check it matches
      if (storedValue === value) {
        checked = 'checked'
      }
    }
    return checked
  })
}

/**
 * Simple basic auth middleware for use with Express 4.x.
 *
 * Based on template found at: http://www.danielstjules.com/2014/08/03/basic-auth-with-express-4/
 *
 * @example
 * app.use('/api-requiring-auth', utils.basicAuth('username', 'password'))
 *
 * @param   {string}   username Expected username
 * @param   {string}   password Expected password
 * @returns {function} Express 4 middleware requiring the given credentials
 */

exports.basicAuth = function (username, password) {
  return function (req, res, next) {
    if (!username || !password) {
      console.log('Username or password is not set.')
      return res.send('
Error:

Username or password not set. See guidance for setting these.
')
    }

    var user = basicAuth(req)

    if (!user || user.name !== username || user.pass !== password) {
      res.set('WWW-Authenticate', 'Basic realm=Authorization Required')
      return res.sendStatus(401)
    }

    next()
  }
}

// Find an available port to run the server on
exports.findAvailablePort = function (app, callback) {
  var port = null

  // When the server starts, we store the port in .port.tmp so it tries to restart
  // on the same port
  try {
    port = Number(fs.readFileSync(path.join(__dirname, '/../.port.tmp')))
  } catch (e) {
    port = Number(process.env.PORT || config.port)
  }

  console.log('')

  // Check port is free, else offer to change
  portScanner.findAPortNotInUse(port, port + 50, '127.0.0.1', function (error, availablePort) {
    if (error) { throw error }
    if (port === availablePort) {
      // Port is free, return it via the callback
      callback(port)
    } else {
      // Port in use - offer to change to available port
      console.error('ERROR: Port ' + port + ' in use - you may have another prototype running.\n')
      // Set up prompt settings
      prompt.colors = false
      prompt.start()
      prompt.message = ''
      prompt.delimiter = ''

      // Ask user if they want to change port
      prompt.get([{
        name: 'answer',
        description: 'Change to an available port? (y/n)',
        required: true,
        type: 'string',
        pattern: /y(es)?|no?/i,
        message: 'Please enter y or n'
      }], function (err, result) {
        if (err) { throw err }
        if (result.answer.match(/y(es)?/i)) {
          // User answers yes
          port = availablePort
          fs.writeFileSync(path.join(__dirname, '/../.port.tmp'), port)
          console.log('Changed to port ' + port)

          callback(port)
        } else {
          // User answers no - exit
          console.log('\nYou can set a new default port in server.js, or by running the server with PORT=XXXX')
          console.log("\nExit by pressing 'ctrl + c'")
          process.exit(0)
        }
      })
    }
  })
}

// Redirect HTTP requests to HTTPS
exports.forceHttps = function (req, res, next) {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    console.log('Redirecting request to https')
    // 302 temporary - this is a feature that can be disabled
    return res.redirect(302, 'https://' + req.get('Host') + req.url)
  }
  next()
}

// Synchronously get the URL for the latest release on GitHub and cache it
exports.getLatestRelease = function () {
  var url = ''

  if (releaseUrl !== null) {
    // Release URL already exists
    console.log('Release url cached:', releaseUrl)
    return releaseUrl
  } else {
    // Release URL doesn't exist
    var options = {
      headers: {'user-agent': 'node.js'}
    }
    var gitHubUrl = 'https://api.github.com/repos/alphagov/govuk-prototype-kit/releases/latest'
    try {
      console.log('Getting latest release from GitHub')

      var res = request('GET', gitHubUrl, options)
      var data = JSON.parse(res.getBody('utf8'))
      var zipballUrl = data['zipball_url']
      var releaseVersion = zipballUrl.split('/').pop()
      var urlStart = 'https://github.com/alphagov/govuk-prototype-kit/archive/'
      var urlEnd = '.zip'
      var zipUrl = urlStart + releaseVersion + urlEnd

      console.log('Release URL is', zipUrl)
      releaseUrl = zipUrl
      url = releaseUrl
    } catch (err) {
      url = 'https://github.com/alphagov/govuk-prototype-kit/releases/latest'
      console.log("Couldn't retrieve release URL")
    }
  }
  return url
}

// Try to match a request to a template, for example a request for /test
// would look for /app/views/test.html
// or /app/views/text/index.html
exports.matchRoutes = function (req, res) {
  var path = (req.params[0])
  res.render(path, function (err, html) {
    if (err) {
      res.render(path + '/index', function (err2, html) {
        if (err2) {
          res.status(404).send(err + '
' + err2)
        } else {
          res.end(html)
        }
      })
    } else {
      res.end(html)
    }
  })
}

// Try to match a request to a Markdown file and render it
exports.matchMdRoutes = function (req, res) {
  var docsPath = '/../docs/documentation/'
  if (fs.existsSync(path.join(__dirname, docsPath, req.params[0] + '.md'), 'utf8')) {
    var doc = fs.readFileSync(path.join(__dirname, docsPath, req.params[0] + '.md'), 'utf8')
    var html = marked(doc)
    res.render('documentation_template', {'document': html})
    return true
  }
  return false
}

// Store data from POST body or GET query in session
var storeData = function (input, store) {
  for (var i in input) {
    // any input where the name starts with _ is ignored
    if (i.indexOf('_') === 0) {
      continue
    }

    var val = input[i]

    // Delete values when users unselect checkboxes
    if (val === '_unchecked' || val === ['_unchecked']) {
      delete store.data[i]
      continue
    }

    // Remove _unchecked from arrays of checkboxes
    if (Array.isArray(val)) {
      var index = val.indexOf('_unchecked')
      if (index !== -1) {
        val.splice(index, 1)
      }
    }

    store.data[i] = val
  }
}

// Get session default data from file
let sessionDataDefaults = {}

const sessionDataDefaultsFile = path.join(__dirname, '/../app/data/session-data-defaults.js')

try {
  sessionDataDefaults = require(sessionDataDefaultsFile)
} catch (e) {
  console.error('Could not load the session data defaults from app/data/session-data-defaults.js. Might be a syntax error?')
}

// Middleware - store any data sent in session, and pass it to all views
exports.autoStoreData = function (req, res, next) {
  if (!req.session.data) {
    req.session.data = {}
  }

  req.session.data = Object.assign({}, sessionDataDefaults, req.session.data)

  storeData(req.body, req.session)
  storeData(req.query, req.session)

  // Send session data to all views

  res.locals.data = {}

  for (var j in req.session.data) {
    res.locals.data[j] = req.session.data[j]
  }

  next()
}

exports.handleCookies = function (app) {
  return function handleCookies (req, res, next) {
    const COOKIE_NAME = 'seen_cookie_message'
    let cookie = req.cookies[COOKIE_NAME]

    if (cookie === 'yes') {
      app.locals.shouldShowCookieMessage = false
      return next()
    }

    let maxAgeInDays = 28
    res.cookie(COOKIE_NAME, 'yes', {
      maxAge: maxAgeInDays * 24 * 60 * 60 * 1000,
      httpOnly: true
    })

    app.locals.shouldShowCookieMessage = true

    next()
  }
}
<|endoftext|>


<commit_before>'use strict';

let _ = require('lodash');
let slug = require('slug');

function slugify(str) {
  if (!_.isString(str)) {
    return str;
  }
  const slugs = slugify.slugs;
  const slugified = slug(str).toLowerCase();
  let uniqueSlug = slugified;
  while (slugs.has(uniqueSlug)) {
    uniqueSlug = _.uniqueId(`${slugified}-`);
  }
  slugs.add(uniqueSlug);
  return uniqueSlug;
}
slugify.slugs = new Set();

function isSingular(value) {
  return _.size(value) === 1;
}

function isMultiple(value) {
  return _.size(value) > 1;
}

const noChainOpts = {
  chain: false
};

_.mixin({
  slugify: _.memoize(slugify),
  isSingular: isSingular,
  isMultiple: isMultiple
}, noChainOpts);

module.exports = _;
<commit_msg>add negated is* functions<commit_after>'use strict';

let _ = require('lodash');
let slug = require('slug');

function slugify(str) {
  if (!_.isString(str)) {
    return str;
  }
  const slugs = slugify.slugs;
  const slugified = slug(str).toLowerCase();
  let uniqueSlug = slugified;
  while (slugs.has(uniqueSlug)) {
    uniqueSlug = _.uniqueId(`${slugified}-`);
  }
  slugs.add(uniqueSlug);
  return uniqueSlug;
}
slugify.slugs = new Set();

function isSingular(value) {
  return _.size(value) === 1;
}

function isMultiple(value) {
  return _.size(value) > 1;
}

const mixins = _(_)
  .functions()
  .filter((funcName) => _.startsWith(funcName, 'is'))
  .map((funcName) => {
    const negatedFuncName = `isNot${funcName.substring(2)}`;
    return [negatedFuncName, _.negate(_[funcName])];
  })
  .object()
  .extend({
    slugify: _.memoize(slugify),
    isSingular: isSingular,
    isMultiple: isMultiple
  })
  .value();

const noChainOpts = {
  chain: false
};

_.mixin(mixins, noChainOpts);

module.exports = _;
<|endoftext|>


<commit_before>function getEmoji(msg, emojiname) {
  let guildEmoji = msg.guild.emojis.array();
  for (let i = 0; i < guildEmoji.length; i++) {
    if (guildEmoji[i].name == emojiname) {
      return guildEmoji[i].toString;
    }
  }
  return "";
}

function getAllEmoji(guild) {
  let ret = {};
  let gem = guild.emojis.array();
  for (let i = 0; i < gem.length; i++) {
    ret[gem[i].name] = gem[i];
  }
  return ret;
}

function hascmd(msg, cmd) {
  return msg.substring(0, "!" + cmd + " ".length).toLowerCase() === "!" + cmd + " ";
}

module.exports = {
  getEmoji,
  getAllEmoji,
  hascmd
}<commit_msg>fixed string comparison error<commit_after>function getEmoji(msg, emojiname) {
  let guildEmoji = msg.guild.emojis.array();
  for (let i = 0; i < guildEmoji.length; i++) {
    if (guildEmoji[i].name == emojiname) {
      return guildEmoji[i].toString;
    }
  }
  return "";
}

function getAllEmoji(guild) {
  let ret = {};
  let gem = guild.emojis.array();
  for (let i = 0; i < gem.length; i++) {
    ret[gem[i].name] = gem[i];
  }
  return ret;
}

function hascmd(msg, cmd) {
  console.log(msg.substring(0, "!" + cmd + " ".length).toLowerCase());
  console.log("!" + cmd + " ");
  return msg.substring(0, "!" + cmd + " ".length).toLowerCase() === "!" + cmd + " ";
}

module.exports = {
  getEmoji,
  getAllEmoji,
  hascmd
}<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';

var colors = require('colors/safe'),
    packageJson = require('../package.json');

exports.queryResultToJson = function(data, status) {
    var wrappedData = {};
    if (data) {
        if (!data.length && data.dataValues) {
            wrappedData = data.dataValues;
        } else if (data.length > 0) {
            wrappedData = [];
            for (var i=0; i<data.length; i++) {
                wrappedData.push(data[i].dataValues ? data[i].dataValues : data[i]);
            }
        } else {
            wrappedData = data;
        }
    }
    return {
        status: status || 'success',
        data: wrappedData
    };
};

exports.startsWith = function(str, prefix) {
    return str ? str.indexOf(prefix) === 0 : false;
};

exports.endsWith = function(str, suffix) {
    return str ? str.indexOf(suffix, str.length - suffix.length) !== -1 : false;
};

exports.contains = function(str, element) {
    return str ? str.indexOf(element) !== -1 : false;
};

exports.unquote = function(str) {
    if (str && this.startsWith(str, '"') && this.endsWith(str, '"')) {
        return str.substring(1, str.length-1);
    }
    else {
        return str;
    }
};

exports.version = function(module) {
    if (module) {
        return packageJson.dependencies[module];
    } else {
        return packageJson.version;
    }
};

exports.solve = function(challenge) {
    challenge.solved = true;
    challenge.save().success(function() {
        console.log(colors.green('Solved') + ' challenge "' + challenge.name + '"');
    });
};

exports.notSolved = function(challenge) {
    return challenge && !challenge.solved;
};<commit_msg>detailed challenge logs<commit_after>/*jslint node: true */
'use strict';

var colors = require('colors/safe'),
    packageJson = require('../package.json');

exports.queryResultToJson = function(data, status) {
    var wrappedData = {};
    if (data) {
        if (!data.length && data.dataValues) {
            wrappedData = data.dataValues;
        } else if (data.length > 0) {
            wrappedData = [];
            for (var i=0; i<data.length; i++) {
                wrappedData.push(data[i].dataValues ? data[i].dataValues : data[i]);
            }
        } else {
            wrappedData = data;
        }
    }
    return {
        status: status || 'success',
        data: wrappedData
    };
};

exports.startsWith = function(str, prefix) {
    return str ? str.indexOf(prefix) === 0 : false;
};

exports.endsWith = function(str, suffix) {
    return str ? str.indexOf(suffix, str.length - suffix.length) !== -1 : false;
};

exports.contains = function(str, element) {
    return str ? str.indexOf(element) !== -1 : false;
};

exports.unquote = function(str) {
    if (str && this.startsWith(str, '"') && this.endsWith(str, '"')) {
        return str.substring(1, str.length-1);
    }
    else {
        return str;
    }
};

exports.version = function(module) {
    if (module) {
        return packageJson.dependencies[module];
    } else {
        return packageJson.version;
    }
};

exports.solve = function(challenge) {
    challenge.solved = true;
    challenge.save().success(function() {
        console.log(colors.green('Solved') + ' challenge ' + colors.cyan(challenge.name) + ' (' + challenge.description + ')');
    });
};

exports.notSolved = function(challenge) {
    return challenge && !challenge.solved;
};<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var fs = require('fs'),
    path = require('path'),
    utils = exports = module.exports;

/**
 * Does path `p` exist?
 *
 * @param {String} p
 * @return {Boolean}
 */

var exists = utils.exists = function(p) {
  return fs.existsSync(p);
};

/**
 * Returns `true` if `p` is a directory
 *
 * @param {String} p
 * @return {Boolean}
 */

var isDir = utils.isDir = function(p) {
  return exists(p) && fs.lstatSync(p).isDirectory();
};

/**
 * Returns `true` if `p` is a file
 *
 * @param {String} p
 * @return {Boolean}
 */

var isFile = utils.isFile = function(p) {
  return exists(p) && fs.lstatSync(p).isFile();
};

/**
 * Chunk `p` into parts
 *
 * @param {String} p
 * @return {Array}
 */

var splitPaths = utils.splitPaths = function(p) {
  return p.split(path.sep);
};

/**
 * Make all directories leading up to `p`
 * like a unix `mkdir -p p`
 *
 * @param {String} p
 * @return {Boolean}
 */

var mkdir = utils.mkdir = function(p) {
  var errors;

  splitPaths(p).reduce(function(dirs, dir) {
    dirs += dir + path.sep;
    var s = path.resolve(dirs);

    if(!exists(s)) {
      try {
        fs.mkdirSync(s);
      } catch(e) {
        errors = true;
      }
    }

    return dirs;
  }, '');

  return !errors;
};

/**
 * Read the path `p` and return an object with files and directories
 *
 * @param {String} p
 * @return {Object}
 */

var readDir = utils.readDir = function(p) {
  if(!isDir(p)) return false;
  var dir = fs.readdirSync(p).map(function(f) {
    return path.join(p, f);
  });

  return {
    files: dir.filter(function(f) {
      return isFile(f);
    }),
    directories: dir.filter(function(f) {
      return isDir(f);
    })
  };
};

/**
 * Read file `p`
 *
 * @param {String} p
 * @return {String|Boolean}
 */

var read = utils.read = function(p) {
  if(!exists(p)) return false;

  try {
    return fs.readFileSync(p);
  } catch(e) {
    return false;
  }
};

/**
 * Write `data` to path `p` creating any directories on the way.
 *
 * @param {String} p
 * @param {String} data
 * @return {Boolean}
 */

var write = utils.write = function(p, data) {
  mkdir(path.dirname(p));

  try {
    fs.writeFileSync(p, data);
    return true;
  } catch(e) {
    return false;
  }
};
<commit_msg>add some new utilities to simplify core<commit_after>/**
 * Module dependencies
 */

var fs = require('fs'),
    path = require('path'),
    utils = exports = module.exports;

/**
 * Does path `p` exist?
 *
 * @param {String} p
 * @return {Boolean}
 */

var exists = utils.exists = function(p) {
  return fs.existsSync(p);
};

/**
 * Returns `true` if `p` is a directory
 *
 * @param {String} p
 * @return {Boolean}
 */

var isDir = utils.isDir = function(p) {
  return exists(p) && fs.lstatSync(p).isDirectory();
};

/**
 * Returns `true` if `p` is a file
 *
 * @param {String} p
 * @return {Boolean}
 */

var isFile = utils.isFile = function(p) {
  return exists(p) && fs.lstatSync(p).isFile();
};

/**
 * Chunk `p` into parts
 *
 * @param {String} p
 * @return {Array}
 */

var splitPaths = utils.splitPaths = function(p) {
  return p.split(path.sep);
};

/**
 * Return the name of file `p`
 *
 * @param {String} p
 * @return {String}
 */

var fileName = utils.fileName = function(p) {
  return path.basename(p, path.extname(p));
};

/**
 * Joins paths `a` and `b`, but replaces `b` ext with html
 *
 * @param {String} a
 * @param {String} b
 * @return {String}
 */

var joinPaths = utils.joinPaths = function(a, b) {
  var f = fileName(b),
      b = path.dirname(b);

  return path.join(a, b, f + '.html');
};

/**
 * Make all directories leading up to `p`
 * like a unix `mkdir -p p`
 *
 * @param {String} p
 * @return {Boolean}
 */

var mkdir = utils.mkdir = function(p) {
  var errors;

  splitPaths(p).reduce(function(dirs, dir) {
    dirs += dir + path.sep;
    var s = path.resolve(dirs);

    if(!exists(s)) {
      try {
        fs.mkdirSync(s);
      } catch(e) {
        errors = true;
      }
    }

    return dirs;
  }, '');

  return !errors;
};

/**
 * Read the path `p` and return an object with files and directories
 *
 * @param {String} p
 * @return {Object}
 */

var readDir = utils.readDir = function(p) {
  if(!isDir(p)) return false;
  var dir = fs.readdirSync(p).map(function(f) {
    return path.join(p, f);
  });

  return {
    files: dir.filter(function(f) {
      return isFile(f);
    }),
    directories: dir.filter(function(f) {
      return isDir(f);
    })
  };
};

/**
 * Read file `p`
 *
 * @param {String} p
 * @return {String|Boolean}
 */

var read = utils.read = function(p) {
  if(!exists(p)) return false;

  try {
    return fs.readFileSync(p, 'utf8');
  } catch(e) {
    return false;
  }
};

/**
 * Write `data` to path `p` creating any directories on the way.
 *
 * @param {String} p
 * @param {String} data
 * @return {Boolean}
 */

var write = utils.write = function(p, data) {
  mkdir(path.dirname(p));

  try {
    fs.writeFileSync(p, data);
    return true;
  } catch(e) {
    return false;
  }
};
<|endoftext|>


<commit_before>var valid = function(el) {
  // jquery the selector
  var $el = $(el);
  // narrow selection to only these validations
  var $els = $el.find('[data-valid-required], [data-valid-pattern], [data-valid-email]');
  // storage array to return
  var arr = [];

  // tests the pattern against the value that is passed
  // in, returns true if there is a match
  var testPattern = function( value, pattern ) {
      var regex = new RegExp(pattern);
      return regex.test(value);
  };

  // check for errors
  var getError = function(element) {
    // jquery the selector
    var $element = $(element);
    // trim the whitespace on inputs
    var $value = $.trim($element.val());
    // set the default error
    var error = false;
    var type = 'required';
    var pattern;

    // test the regex pattern
    if ($element.data('valid-pattern')) {
      type = 'pattern';
      pattern = $element.data('valid-pattern');
      // test() will return true for a match
      // want to reverse that so it reads like english
      // i.e. if there is an error, set error to true
      error = ! testPattern($element.val(), pattern);
    }
    // test the email pattern
    if ($element.data('valid-email') === '') {
        type = 'email';
        pattern = /[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
        //run the pattern check with this pattern
        // needs to return true if there is an error
        error = ! testPattern($element.val(), pattern);
    }

    // this will check for an empty value
    // covers the required case
    if ($value.length === 0) {
      error = true;
    }

    if (!error) {
      return false;
    }

    return {
      'element' : $element[0],
      'type' : type
    };
  };

  $.each($els, function() {
    var $el = $(this);
    var err = getError($el);
    if (err) {
      arr.push(err);
    }
  });
  return arr;
};
<commit_msg>Add comments within code<commit_after>var valid = function(el) {
  // jquery the selector
  var $el = $(el);
  // narrow selection to only these validations
  var $els = $el.find('[data-valid-required], [data-valid-pattern], [data-valid-email]');
  // storage array to return
  var arr = [];

  // tests the pattern against the value that is passed
  // return true if there is a match
  var testPattern = function( value, pattern ) {
      var regex = new RegExp(pattern);
      return regex.test(value);
  };

  // check for errors with the passed in element
  var getError = function(element) {
    // jquery the selector
    var $element = $(element);
    // trim the whitespace on inputs
    var $value = $.trim($element.val());
    // set the default error
    var error = false;
    // the type; pattern, email, url, or required
    var type;
    // the regex pattern to test
    var pattern;

    // test the regex pattern
    if ($element.data('valid-pattern')) {
      type = 'pattern';
      // grab the pattern that is passed in from the view
      pattern = $element.data('valid-pattern');
      // test() will return true for a match
      // want to reverse that so it reads like english
      // i.e. if there is an error, set error to true
      error = ! testPattern($element.val(), pattern);
    }
    // test the email pattern
    if ($element.data('valid-email') === '') {
        type = 'email';
        // this pattern came from a site that specified that
        // this will match the RFC 5322 spec
        pattern = /[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
        //run the pattern check with this pattern
        // needs to return true if there is an error
        error = ! testPattern($element.val(), pattern);
    }

    // this will check for an empty value
    // covers the required case
    if ($element.data('valid-required') === '' && $value.length === 0) {
      type = 'required';
      error = true;
    }

    // @TODO: find a way to kill this
    // ideally should only have 1 exit point per function
    if (!error) {
      return false;
    }

    return {
      'element' : $element[0],
      'type' : type
    };
  };

  $.each($els, function() {
    var $el = $(this);
    var err = getError($el);
    if (err) {
      arr.push(err);
    }
  });
  return arr;
};
<|endoftext|>


<commit_before>// Load modules

var Fs = require('fs');
var Path = require('path');
var Defaults = require('./defaults');
var Utils = require('./utils');
var Err = require('./error');
// Additional engine modules required in constructor

// Declare internals

var internals = {};

// Engines Manager

internals.Engines = function (options) {

    this.settings = options || {};
    this._e = {};
};

internals.Engines.prototype.add = function (settings, key) {

    key = key || settings.extension || null;

    Utils.assert(settings.module, 'Engine.module must be defined');
    Utils.assert(key, 'Engine.extension must be defined');

    if (typeof settings.module == 'string') {
        try {
            this._e[key] = require(settings.module);  // Can only require modules from hapi's package.json
        }
        catch (e) {
            delete this._e[key];
            Utils.assert(false, 'Engine.module (' + settings.module + ') must be installed');
        }
    }
    else {
        this._e[key] = settings.module;
    }
    this._e[key].suffix = '.' + key;

    // Generalize engine support via map object

    var funcs = Object.keys(settings.map);
    for(var i = 0, il = funcs.length; i < il; ++i) {
        if (settings.map.hasOwnProperty(funcs[i])) {
            this._e[key][funcs[i]] = settings.map[funcs[i]](this._e[key]);
        }
    }

    return this._e[key];
};

internals.Engines.prototype.getExt = function (p) {

    if (p.indexOf('.') >= 0) {
        p = Path.extname(p).slice(1);
    }

    return p;
};

internals.Engines.prototype.get = function (extension) {

    return this._e[this.getExt(extension)];
};

internals.Engines.prototype.keys = function () {

    return Object.keys(this._e);
};

// View Manager

exports = module.exports = internals.Manager = function (options) {

    // this.settings = Utils.applyToDefaults(Defaults.views, options);
    this.settings = this.mergeLeft(Defaults.views, options);

    Utils.assert(!this.settings.partials || this.settings.partials.path, 'Missing partials path');

    this.engines = new internals.Engines();                             // Multiple engine support (distinguished by extension)
    this._cache = {};                                                   // Compiled templates cache

    this.loadEngines();
    this.loadPartials();

    return this;
};

internals.Manager.prototype.mergeLeft = function () {

    var args = Array.prototype.slice.call(arguments);
    var base = Utils.clone(args.shift());

    args.forEach(function(el, i, arr) {

        for(var key in el) {
            base[key] = el[key];
        }
    });
    return base;
};

internals.Manager.prototype.loadEngines = function () {

    this.settings.engines = this.settings.engines || {};

    // Support API backwards compatibility

    if (this.settings.engine) {
        this.settings.engines[this.settings.engine.extension || Defaults.views.engine.extension] = this.settings.engine;
        delete this.settings.engine;
    }

    // Load all engines

    var engines = Object.keys(this.settings.engines);
    for(var i = 0, il = engines.length; i < il; ++i) {
        var key = engines[i];
        var engine = this.settings.engines[key];
        if (!engine.extension) {
            engine.extension = key;
        }

        // Merge Base

        var base = Utils.clone(Defaults.views.engines.html);
        var baseMethods = Object.keys(base);
        for(var j = 0, jl = baseMethods.length; j < jl; ++j) {
            var method = baseMethods[j];
            if (!engine.hasOwnProperty(method)) {
                engine[method] = base[method];
            }
        }

        this.engines.add(engine);
    }
};

internals.Manager.prototype.render = function (template, context, options) {

    if (typeof template === 'string') {
        template = this._get(template, options);
        if (template instanceof Error) {
            return template;
        }
    }

    if (!this.settings.layout) {

        // No layout

        try {
            return template(context);
        }
        catch (err) {
            return Err.internal(err.message, err);
        }
    }

    // With layout

    if (context &&
        context.hasOwnProperty(this.settings.layoutKeyword)) {

        return Err.internal('settings.layoutKeyword conflict', { context: context, keyword: this.settings.layoutKeyword });
    }

    var layout = this._get('layout', options);
    if (layout instanceof Error) {
        return layout;
    }

    var layoutContext = Utils.clone(context);

    try {
        layoutContext[this.settings.layoutKeyword] = template(context);
        return layout(layoutContext);
    }
    catch (err) {
        return Err.internal(err.message, err);
    }
};

internals.Manager.prototype._isCaching = function (filename, globalSettings, engineSettings, set) {

    // globalSettings = globalSettings || this.settings;
    set = set || false;
    engineSettings = this.mergeLeft(Defaults.views.engines.html, engineSettings || {});

    if (globalSettings.cache == false) {
        return false; // if global cache disabled, disable all caching behavior
    }

    if (engineSettings.cache == false) {
        return false; // if engine cache disabled, disable this request
    }

    if (set) {
        return true;
    }

    return this._cache.hasOwnProperty(filename); // Cache enabled only if key exists
};

internals.Manager.prototype.getCache = function (filename) {

    return this._cache[filename];
};

internals.Manager.prototype.setCache = function (filename, compiled, options, engineSettings) {

    if (this._isCaching(filename, options, engineSettings, true)) {
        return this._cache[filename] = compiled;
    }
};

internals.Manager.prototype._get = function (filename, options) {

    options = this.mergeLeft(this.settings, options);

    if (this._isCaching(filename, options)) {
        return this.getCache(filename);
    }

    // Normalize path

    var isAbsolutePath = (filename.indexOf('/') === 0);
    var isInsecurePath = filename.match(/\.\.\//g) !== null;

    if (!options.allowAbsolutePaths &&
        isAbsolutePath) {

        return Err.internal('Absolute paths are not allowed in views');
    }

    if (!options.allowInsecureAccess &&
        isInsecurePath) {

        return Err.internal('View paths cannot lookup templates outside root path (path includes one or more \'../\')');
    }

    // Resolve Path and extension

    var fullPath = filename;
    if (!isAbsolutePath) {
        fullPath = Path.join(options.path, fullPath);
    }

    var folder = Path.dirname(fullPath);
    var files = Fs.readdirSync(folder);
    var re = new RegExp(Path.basename(filename) + '([.][a-zA-Z0-9]+)?$');

    for (var j = 0, jl = files.length; j < jl; ++j) {
        var match = re.exec(files[j]);
        if (match) {
            fullPath = Path.join(folder, files[j]);
            break;
        }
    }

    // Pass the filename to Jade via this copy of compileOptions

    options.compileOptions.filename = fullPath;

    // Read file

    try {
        var source = Fs.readFileSync(fullPath).toString(this.settings.encoding);
    }
    catch (e) {
        return Err.internal('View file not found: ' + fullPath);
    }

    var engine = this.engines.get(fullPath);
    var compiled = engine.compile(source, options.compileOptions);

    this.setCache(filename, compiled, options, this.settings.engines[engine.suffix.slice(1)]);

    return compiled;
};

internals.Manager.prototype.loadPartials = function () {

    var self = this;
    if (this.settings.partials) {
        this._loadPartials(this.settings.partials.path || this.settings.path, function (files) {

            for (var i = 0, il = files.length; i < il; ++i) {
                var file = files[i];
                var engine = self.engines.get(file);
                if (!engine.hasOwnProperty('registerPartial')) {
                    continue;
                }

                var name = file;
                if (name.indexOf(self.settings.partials.path) >= 0) {
                    var offset = 1;
                    if (self.settings.partials.path.slice(-1) == '/') {
                        offset = 0;
                    }
                    name = name.slice(self.settings.partials.path.length + offset, -engine.suffix.length);
                }

                // Remove when handlebars npm module gets updated

                if (engine.VERSION && engine.VERSION.indexOf('1.0.rc') >= 0) {

                    name = name.replace(/\//g, engine.slashReplacement || '-');
                }

                var src = Fs.readFileSync(file).toString(self.settings.encoding);
                console.log('saving partial', name, src)
                engine.registerPartial(name, src);
            }
        });
    }
};

internals.Manager.prototype._loadPartials = function (path, callback) {

    var self = this;
    var suffixes = Object.keys(this.engines.keys());

    var walk = function (root, currentPath, files) {

        var current = Path.join(root, currentPath);
        if (!Fs.statSync(current).isDirectory()) {
            try {
                var engine = self.engines.get(current);
                if (current.slice(-engine.suffix.length) === engine.suffix) {
                    files.push(current);
                }
            }
            catch (e) {
                // Skip file
            }

            return callback(files);
        }

        var directory = Fs.readdirSync(current);
        for (var i = 0, il = directory.length; i < il; ++i) {
            var currentFile = directory[i];
            var currentFileFullPath = Path.join(root, currentFile);
            if (Fs.statSync(currentFileFullPath).isDirectory()) {
                walk(currentFileFullPath, '/', files);
            }
            else {
                walk(root, currentFile, files);
            }
        }

        return callback(files);
    };

    walk(path, '/', []);
};

exports.handler = function (route, viewFilePath) {

    return function (request) {

        var context = {
            params: request.params,
            payload: request.payload,
            querystring: request.querystring
        };

        request.reply.view(viewFilePath, context).send();
    };
};
<commit_msg>cleanup<commit_after>// Load modules

var Fs = require('fs');
var Path = require('path');
var Defaults = require('./defaults');
var Utils = require('./utils');
var Err = require('./error');
// Additional engine modules required in constructor

// Declare internals

var internals = {};

// Engines Manager

internals.Engines = function (options) {

    this.settings = options || {};
    this._e = {};
};

internals.Engines.prototype.add = function (settings, key) {

    key = key || settings.extension || null;

    Utils.assert(settings.module, 'Engine.module must be defined');
    Utils.assert(key, 'Engine.extension must be defined');

    if (typeof settings.module == 'string') {
        try {
            this._e[key] = require(settings.module);  // Can only require modules from hapi's package.json
        }
        catch (e) {
            delete this._e[key];
            Utils.assert(false, 'Engine.module (' + settings.module + ') must be installed');
        }
    }
    else {
        this._e[key] = settings.module;
    }
    this._e[key].suffix = '.' + key;

    // Generalize engine support via map object

    var funcs = Object.keys(settings.map);
    for(var i = 0, il = funcs.length; i < il; ++i) {
        if (settings.map.hasOwnProperty(funcs[i])) {
            this._e[key][funcs[i]] = settings.map[funcs[i]](this._e[key]);
        }
    }

    return this._e[key];
};

internals.Engines.prototype.getExt = function (p) {

    if (p.indexOf('.') >= 0) {
        p = Path.extname(p).slice(1);
    }

    return p;
};

internals.Engines.prototype.get = function (extension) {

    return this._e[this.getExt(extension)];
};

internals.Engines.prototype.keys = function () {

    return Object.keys(this._e);
};

// View Manager

exports = module.exports = internals.Manager = function (options) {

    // this.settings = Utils.applyToDefaults(Defaults.views, options);
    this.settings = this.mergeLeft(Defaults.views, options);

    Utils.assert(!this.settings.partials || this.settings.partials.path, 'Missing partials path');

    this.engines = new internals.Engines();                             // Multiple engine support (distinguished by extension)
    this._cache = {};                                                   // Compiled templates cache

    this.loadEngines();
    this.loadPartials();

    return this;
};

internals.Manager.prototype.mergeLeft = function () {

    var args = Array.prototype.slice.call(arguments);
    var base = Utils.clone(args.shift());

    args.forEach(function(el, i, arr) {

        for(var key in el) {
            base[key] = el[key];
        }
    });
    return base;
};

internals.Manager.prototype.loadEngines = function () {

    this.settings.engines = this.settings.engines || {};

    // Support API backwards compatibility

    if (this.settings.engine) {
        this.settings.engines[this.settings.engine.extension || Defaults.views.engines.html.extension] = this.settings.engine;
        delete this.settings.engine;
    }

    // Load all engines

    var engines = Object.keys(this.settings.engines);
    for(var i = 0, il = engines.length; i < il; ++i) {
        var key = engines[i];
        var engine = this.settings.engines[key];
        if (!engine.extension) {
            engine.extension = key;
        }

        // Merge Base

        var base = Utils.clone(Defaults.views.engines.html);
        var baseMethods = Object.keys(base);
        for(var j = 0, jl = baseMethods.length; j < jl; ++j) {
            var method = baseMethods[j];
            if (!engine.hasOwnProperty(method)) {
                engine[method] = base[method];
            }
        }

        this.engines.add(engine);
    }
};

internals.Manager.prototype.render = function (template, context, options) {

    if (typeof template === 'string') {
        template = this._get(template, options);
        if (template instanceof Error) {
            return template;
        }
    }

    if (!this.settings.layout) {

        // No layout

        try {
            return template(context);
        }
        catch (err) {
            return Err.internal(err.message, err);
        }
    }

    // With layout

    if (context &&
        context.hasOwnProperty(this.settings.layoutKeyword)) {

        return Err.internal('settings.layoutKeyword conflict', { context: context, keyword: this.settings.layoutKeyword });
    }

    var layout = this._get('layout', options);
    if (layout instanceof Error) {
        return layout;
    }

    var layoutContext = Utils.clone(context);

    try {
        layoutContext[this.settings.layoutKeyword] = template(context);
        return layout(layoutContext);
    }
    catch (err) {
        return Err.internal(err.message, err);
    }
};

internals.Manager.prototype._isCaching = function (filename, globalSettings, engineSettings, set) {

    set = set || false;
    engineSettings = this.mergeLeft(Defaults.views.engines.html, engineSettings || {});

    if (globalSettings.cache == false) {
        return false; // if global cache disabled, disable all caching behavior
    }

    if (engineSettings.cache == false) {
        return false; // if engine cache disabled, disable this request
    }

    if (set) {
        return true;
    }

    return this._cache.hasOwnProperty(filename); // Cache enabled only if key exists
};

internals.Manager.prototype.getCache = function (filename) {

    return this._cache[filename];
};

internals.Manager.prototype.setCache = function (filename, compiled, options, engineSettings) {

    if (this._isCaching(filename, options, engineSettings, true)) {
        return this._cache[filename] = compiled;
    }
};

internals.Manager.prototype._get = function (filename, options) {

    options = this.mergeLeft(this.settings, options);

    if (this._isCaching(filename, options)) {
        return this.getCache(filename);
    }

    // Normalize path

    var isAbsolutePath = (filename.indexOf('/') === 0);
    var isInsecurePath = filename.match(/\.\.\//g) !== null;

    if (!options.allowAbsolutePaths &&
        isAbsolutePath) {

        return Err.internal('Absolute paths are not allowed in views');
    }

    if (!options.allowInsecureAccess &&
        isInsecurePath) {

        return Err.internal('View paths cannot lookup templates outside root path (path includes one or more \'../\')');
    }

    // Resolve Path and extension

    var fullPath = filename;
    if (!isAbsolutePath) {
        fullPath = Path.join(options.path, fullPath);
    }

    var folder = Path.dirname(fullPath);
    var files = Fs.readdirSync(folder);
    var re = new RegExp(Path.basename(filename) + '([.][a-zA-Z0-9]+)?$');

    for (var j = 0, jl = files.length; j < jl; ++j) {
        var match = re.exec(files[j]);
        if (match) {
            fullPath = Path.join(folder, files[j]);
            break;
        }
    }

    // Pass the filename to Jade via this copy of compileOptions

    options.compileOptions.filename = fullPath;

    // Read file

    try {
        var source = Fs.readFileSync(fullPath).toString(this.settings.encoding);
    }
    catch (e) {
        return Err.internal('View file not found: ' + fullPath);
    }

    var engine = this.engines.get(fullPath);
    var compiled = engine.compile(source, options.compileOptions);

    this.setCache(filename, compiled, options, this.settings.engines[engine.suffix.slice(1)]);

    return compiled;
};

internals.Manager.prototype.loadPartials = function () {

    var self = this;
    if (this.settings.partials) {
        this._loadPartials(this.settings.partials.path || this.settings.path, function (files) {

            for (var i = 0, il = files.length; i < il; ++i) {
                var file = files[i];
                var engine = self.engines.get(file);
                if (!engine.hasOwnProperty('registerPartial')) {
                    continue;
                }

                var name = file;
                if (name.indexOf(self.settings.partials.path) >= 0) {
                    var offset = 1;
                    if (self.settings.partials.path.slice(-1) == '/') {
                        offset = 0;
                    }
                    name = name.slice(self.settings.partials.path.length + offset, -engine.suffix.length);
                }

                // Remove when handlebars npm module gets updated

                if (engine.VERSION && engine.VERSION.indexOf('1.0.rc') >= 0) {

                    name = name.replace(/\//g, engine.slashReplacement || '-');
                }

                var src = Fs.readFileSync(file).toString(self.settings.encoding);
                engine.registerPartial(name, src);
            }
        });
    }
};

internals.Manager.prototype._loadPartials = function (path, callback) {

    var self = this;
    var suffixes = Object.keys(this.engines.keys());

    var walk = function (root, currentPath, files) {

        var current = Path.join(root, currentPath);
        if (!Fs.statSync(current).isDirectory()) {
            try {
                var engine = self.engines.get(current);
                if (current.slice(-engine.suffix.length) === engine.suffix) {
                    files.push(current);
                }
            }
            catch (e) {
                // Skip file
            }

            return callback(files);
        }

        var directory = Fs.readdirSync(current);
        for (var i = 0, il = directory.length; i < il; ++i) {
            var currentFile = directory[i];
            var currentFileFullPath = Path.join(root, currentFile);
            if (Fs.statSync(currentFileFullPath).isDirectory()) {
                walk(currentFileFullPath, '/', files);
            }
            else {
                walk(root, currentFile, files);
            }
        }

        return callback(files);
    };

    walk(path, '/', []);
};

exports.handler = function (route, viewFilePath) {

    return function (request) {

        var context = {
            params: request.params,
            payload: request.payload,
            querystring: request.querystring
        };

        request.reply.view(viewFilePath, context).send();
    };
};
<|endoftext|>


<commit_before>/**
 * Watch.
 */

'use strict';

/**
 * Module dependencies.
 */

var events = require('events');
var util = require('util');

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

/**
 * Initialize a new `Watch` instance.
 */

function Watch(consul, opts) {
  var self = this;

  events.EventEmitter.call(self);

  opts = utils.normalizeKeys(opts);

  var options = utils.normalizeKeys(opts.options || {});
  options = utils.defaults(options, consul._defaults);
  options.wait = options.wait || '30s';
  options.index = options.index || '0';

  var backoffFactor = 100;
  if (opts.hasOwnProperty('backofffactor') && typeof opts.backofffactor === 'number') {
    backoffFactor = opts.backofffactor;
  }
  var backoffMax = 30 * 1000;
  if (opts.hasOwnProperty('backoffmax') && typeof opts.backoffmax === 'number') {
    backoffMax = opts.backoffmax;
  }
  var maxAttempts = -1;
  if (opts.hasOwnProperty('maxattempts') && typeof opts.maxattempts === 'number') {
    maxAttempts = opts.maxattempts;
  }

  self._context = { consul: consul };
  self._options = options;
  self._attempts = 0;
  self._maxAttempts = maxAttempts;
  self._backoffMax = backoffMax;
  self._backoffFactor = backoffFactor;
  self._method = opts.method;

  if (typeof opts.method !== 'function') {
    throw errors.Validation('method required');
  }

  process.nextTick(function() { self._run(); });
}

util.inherits(Watch, events.EventEmitter);

/**
 * Object meta
 */

Watch.meta = {};

/**
 * Is running
 */

Watch.meta.isRunning = { type: 'sync' };

Watch.prototype.isRunning = function() {
  return !this._end;
};

/**
 * Update time
 */

Watch.meta.updateTime = { type: 'sync' };

Watch.prototype.updateTime = function() {
  return this._updateTime;
};

/**
 * End watch
 */

Watch.meta.end = { type: 'sync' };

Watch.prototype.end = function() {
  if (this._end) return;
  this._end = true;

  this.emit('cancel');
  this.emit('end');
};

/**
 * Wait
 */

Watch.prototype._wait = function() {
  this._attempts += 1;
  if (this._attemptsMaxed) {
    return this._backoffMax;
  }
  var timeout = Math.pow(2, this._attempts) * this._backoffFactor;
  if (timeout < this._backoffMax) {
    return timeout;
  } else {
    this._attemptsMaxed = true;
    return this._backoffMax;
  }
};

/**
 * Error helper
 */

Watch.prototype._err = function(err, res) {
  var self = this;

  if (self._end) return;

  self.emit('error', err, res);

  if (err && err.isValidation) return self.end();
  if (res && res.statusCode === 400) return self.end();
  if (self._maxAttempts >= 0 && self._attempts >= self._maxAttempts) return self.end();

  utils.setTimeoutContext(function() { self._run(); }, self, self._wait());
};

/**
 * Run
 */

Watch.prototype._run = function() {
  var self = this;

  if (self._end) return;

  var opts = utils.clone(self._options);
  opts.ctx = self;

  try {
    self._method.call(self._context, opts, function(err, data, res) {
      if (err) {
        return self._err(err, res);
      }

      self._updateTime = +new Date();

      self._attempts = 0;
      self._attemptsMaxed = false;

      var newIndex = res.headers['x-consul-index'];

      if (newIndex === undefined) {
        return self._err(errors.Validation('Watch not supported'), res);
      }

      if (utils.hasIndexChanged(newIndex, self._options.index)) {
        self._options.index = newIndex;

        self.emit('change', data, res);
      }

      process.nextTick(function() { self._run(); });
    });
  } catch (err) {
    self._err(err);
  }
};

/**
 * Module exports.
 */

exports.Watch = Watch;
<commit_msg>Add default timeout to watch<commit_after>/**
 * Watch.
 */

'use strict';

/**
 * Module dependencies.
 */

var events = require('events');
var util = require('util');

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

/**
 * Initialize a new `Watch` instance.
 */

function Watch(consul, opts) {
  var self = this;

  events.EventEmitter.call(self);

  opts = utils.normalizeKeys(opts);

  var options = utils.normalizeKeys(opts.options || {});
  options = utils.defaults(options, consul._defaults);
  options.wait = options.wait || '30s';
  options.index = options.index || '0';
  options.timeout = typeof options.timeout === 'number' ?
    options.timeout :
    (utils.parseDuration(options.wait) + 500);

  var backoffFactor = 100;
  if (opts.hasOwnProperty('backofffactor') && typeof opts.backofffactor === 'number') {
    backoffFactor = opts.backofffactor;
  }
  var backoffMax = 30 * 1000;
  if (opts.hasOwnProperty('backoffmax') && typeof opts.backoffmax === 'number') {
    backoffMax = opts.backoffmax;
  }
  var maxAttempts = -1;
  if (opts.hasOwnProperty('maxattempts') && typeof opts.maxattempts === 'number') {
    maxAttempts = opts.maxattempts;
  }

  self._context = { consul: consul };
  self._options = options;
  self._attempts = 0;
  self._maxAttempts = maxAttempts;
  self._backoffMax = backoffMax;
  self._backoffFactor = backoffFactor;
  self._method = opts.method;

  if (typeof opts.method !== 'function') {
    throw errors.Validation('method required');
  }

  process.nextTick(function() { self._run(); });
}

util.inherits(Watch, events.EventEmitter);

/**
 * Object meta
 */

Watch.meta = {};

/**
 * Is running
 */

Watch.meta.isRunning = { type: 'sync' };

Watch.prototype.isRunning = function() {
  return !this._end;
};

/**
 * Update time
 */

Watch.meta.updateTime = { type: 'sync' };

Watch.prototype.updateTime = function() {
  return this._updateTime;
};

/**
 * End watch
 */

Watch.meta.end = { type: 'sync' };

Watch.prototype.end = function() {
  if (this._end) return;
  this._end = true;

  this.emit('cancel');
  this.emit('end');
};

/**
 * Wait
 */

Watch.prototype._wait = function() {
  this._attempts += 1;
  if (this._attemptsMaxed) {
    return this._backoffMax;
  }
  var timeout = Math.pow(2, this._attempts) * this._backoffFactor;
  if (timeout < this._backoffMax) {
    return timeout;
  } else {
    this._attemptsMaxed = true;
    return this._backoffMax;
  }
};

/**
 * Error helper
 */

Watch.prototype._err = function(err, res) {
  var self = this;

  if (self._end) return;

  self.emit('error', err, res);

  if (err && err.isValidation) return self.end();
  if (res && res.statusCode === 400) return self.end();
  if (self._maxAttempts >= 0 && self._attempts >= self._maxAttempts) return self.end();

  utils.setTimeoutContext(function() { self._run(); }, self, self._wait());
};

/**
 * Run
 */

Watch.prototype._run = function() {
  var self = this;

  if (self._end) return;

  var opts = utils.clone(self._options);
  opts.ctx = self;

  try {
    self._method.call(self._context, opts, function(err, data, res) {
      if (err) {
        return self._err(err, res);
      }

      self._updateTime = +new Date();

      self._attempts = 0;
      self._attemptsMaxed = false;

      var newIndex = res.headers['x-consul-index'];

      if (newIndex === undefined) {
        return self._err(errors.Validation('Watch not supported'), res);
      }

      if (utils.hasIndexChanged(newIndex, self._options.index)) {
        self._options.index = newIndex;

        self.emit('change', data, res);
      }

      process.nextTick(function() { self._run(); });
    });
  } catch (err) {
    self._err(err);
  }
};

/**
 * Module exports.
 */

exports.Watch = Watch;
<|endoftext|>