<commit_before>var TT = (function () {

  var pub = {};

  pub.Templates = {};
  pub.Columns = {};
  pub.Filters = {};
  pub.Layout = {};
  pub.Projects = {};
  pub.Stories = {};
  pub.Users = {};

  pub.noop = function () {};

  // client-side data transformation

  pub.addUser = function (user) {
    pub.Users[user.id] = {
      id: user.id,
      initials: user.person.initials,
      name: user.person.name
    };
  };

  pub.addStory = function (story) {
    story.name = TT.Utils.showdownLite(story.name);
    story.description = story.description.length ? TT.Utils.showdownLite(story.description) : '';
    story.estimate = story.estimate >= 0 ? story.estimate : '';
    story.initials = pub.Utils.usernameToInitials(story.owned_by);
    story.project_name = pub.Utils.generateInitials(pub.getProjectNameFromID(story.project_id));
    story.project_classname = pub.Utils.cssify(pub.getProjectNameFromID(story.project_id));
    story.labels = story.labels ? story.labels.indexOf(',') !== -1 ? story.labels.split(',') : [story.labels] : [];
    if (story.notes && story.notes.note) {
      story.notes = story.notes.note;
    }
    pub.Stories[story.id] = story;
  };

  pub.addProject = function (project) {
    project.active = true;
    pub.Projects[project.id] = project;
  };

  pub.addColumn = function (column) {
    column.class_name = 'column-' + pub.Utils.cssify(column.name);
    pub.Columns[column.name] = column;
  };

  pub.addFilter = function (filter) {
    if (!pub.Filters[filter.name]) {
      filter.element = TT.View.drawFilter(filter);
      filter.active = true;
      pub.Filters[filter.name] = filter;
    } else if (pub.Filters[filter.name].active === false) {
      TT.UI.reactivateFilter(filter.name);
    }
  };

  // helpers

  pub.getProjectNameFromID = function (id) {
    return pub.Projects[id].name;
  };

  pub.setProjectActiveState = function () {
    $('#projects input').each(function () {
      var id = $(this).val();
      var active = $(this).is(':checked');

      pub.Projects[id].active = active;
      if (active) {
        $(this).closest('.project').removeClass('inactive');
      } else {
        $(this).closest('.project').addClass('inactive');
      }
    });
  };

  pub.projectIsActive = function (project_id) {
    return !!pub.Projects[project_id].active;
  };

  pub.storyIsNotFiltered = function (story) {
    var result = true;
    $.each(pub.Filters, function (index, filter) {
      if (result && filter.active && !filter.fn(story)) {
        result = false;
      }
    });

    return result;
  };

  pub.hasTag = function (story, tag) {
    if (story.labels && tag) {
      return $.inArray(tag, story.labels) !== -1;
    }
    return false;
  };

  pub.addTag = function (tags, tag) {
    if ($.inArray(tag, tags) === -1) {
      tags[tags.length] = tag;
    }
    return tags;
  };

  pub.removeTag = function (tags, tag) {
    return TT.Utils.removeFromArray(tags, tag);
  };

  pub.initLayout = function () {
    pub.Layout = ['Backlog', 'Started', 'In QA', 'Passed QA', 'Delivered', 'Accepted'];
  };

  pub.updateColumnDimensions = function () {
    var $window = $(window);
    var $columns = $('#columns .column');

    var padding = 26;
    var height = $window.height() - ($('.column-bucket').offset().top + padding);
    $('.column-bucket').height(height);

    var column_count = $columns.length;
    var width = Math.max(200, Math.round(($window.width() - 24 - (column_count * 8)) / column_count));
    $columns.width(width);

    $('#columns').width((width + 8) * column_count);
  };

  pub.requestProjectsAndIterations = function () {
    TT.Ajax.start();
    $.get('/projects', function (projects) {
      projects = JSON.parse(projects).project;
      TT.API.addProjects(projects);
      TT.Ajax.end();
      TT.View.drawProjectList(projects);
      pub.requestAllIterations();
    });
  };

  pub.requestAllIterations = function () {
    $.each(pub.Projects, function (index, project) {
      TT.Ajax.start();
      $.get('/iterations', { project: project.id }, function (iterations) {
        iterations = JSON.parse(iterations).iteration;
        TT.API.addIterations(iterations);
        TT.Ajax.end();
        TT.View.drawStories();
      });
    });
    pub.updateColumnDimensions();
  };

  pub.onDomReady = function () {
    pub.initLayout();

    pub.View.drawColumns();
    pub.View.drawAccountNav();

    pub.updateColumnDimensions();
    $(window).resize(pub.updateColumnDimensions);

    TT.DragAndDrop.init();
    TT.Search.init();

    pub.requestProjectsAndIterations();
  };

  return pub;

}());
<commit_msg>AC: Namespace cleanup<commit_after>var TT = (function () {

  var pub = {};

  pub.Templates = {};
  pub.Columns = {};
  pub.Filters = {};
  pub.Layout = {};
  pub.Projects = {};
  pub.Stories = {};
  pub.Users = {};

  pub.noop = function () {};

  // client-side data transformation

  pub.addUser = function (user) {
    pub.Users[user.id] = {
      id: user.id,
      initials: user.person.initials,
      name: user.person.name
    };
  };

  pub.addStory = function (story) {
    story.name = TT.Utils.showdownLite(story.name);
    story.description = story.description.length ? TT.Utils.showdownLite(story.description) : '';
    story.estimate = story.estimate >= 0 ? story.estimate : '';
    story.initials = TT.Utils.usernameToInitials(story.owned_by);
    story.project_name = TT.Utils.generateInitials(pub.getProjectNameFromID(story.project_id));
    story.project_classname = TT.Utils.cssify(pub.getProjectNameFromID(story.project_id));
    story.labels = story.labels ? story.labels.indexOf(',') !== -1 ? story.labels.split(',') : [story.labels] : [];
    if (story.notes && story.notes.note) {
      story.notes = story.notes.note;
    }
    pub.Stories[story.id] = story;
  };

  pub.addProject = function (project) {
    project.active = true;
    pub.Projects[project.id] = project;
  };

  pub.addColumn = function (column) {
    column.class_name = 'column-' + pub.Utils.cssify(column.name);
    pub.Columns[column.name] = column;
  };

  pub.addFilter = function (filter) {
    if (!pub.Filters[filter.name]) {
      filter.element = TT.View.drawFilter(filter);
      filter.active = true;
      pub.Filters[filter.name] = filter;
    } else if (pub.Filters[filter.name].active === false) {
      TT.UI.reactivateFilter(filter.name);
    }
  };

  // helpers

  pub.getProjectNameFromID = function (id) {
    return pub.Projects[id].name;
  };

  pub.setProjectActiveState = function () {
    $('#projects input').each(function () {
      var id = $(this).val();
      var active = $(this).is(':checked');

      pub.Projects[id].active = active;
      if (active) {
        $(this).closest('.project').removeClass('inactive');
      } else {
        $(this).closest('.project').addClass('inactive');
      }
    });
  };

  pub.projectIsActive = function (project_id) {
    return !!pub.Projects[project_id].active;
  };

  pub.storyIsNotFiltered = function (story) {
    var result = true;
    $.each(pub.Filters, function (index, filter) {
      if (result && filter.active && !filter.fn(story)) {
        result = false;
      }
    });

    return result;
  };

  pub.hasTag = function (story, tag) {
    if (story.labels && tag) {
      return $.inArray(tag, story.labels) !== -1;
    }
    return false;
  };

  pub.addTag = function (tags, tag) {
    if ($.inArray(tag, tags) === -1) {
      tags[tags.length] = tag;
    }
    return tags;
  };

  pub.removeTag = function (tags, tag) {
    return TT.Utils.removeFromArray(tags, tag);
  };

  pub.initLayout = function () {
    pub.Layout = ['Backlog', 'Started', 'In QA', 'Passed QA', 'Delivered', 'Accepted'];
  };

  pub.updateColumnDimensions = function () {
    var $window = $(window);
    var $columns = $('#columns .column');

    var padding = 26;
    var height = $window.height() - ($('.column-bucket').offset().top + padding);
    $('.column-bucket').height(height);

    var column_count = $columns.length;
    var width = Math.max(200, Math.round(($window.width() - 24 - (column_count * 8)) / column_count));
    $columns.width(width);

    $('#columns').width((width + 8) * column_count);
  };

  pub.requestProjectsAndIterations = function () {
    TT.Ajax.start();
    $.get('/projects', function (projects) {
      projects = JSON.parse(projects).project;
      TT.API.addProjects(projects);
      TT.Ajax.end();
      TT.View.drawProjectList(projects);
      pub.requestAllIterations();
    });
  };

  pub.requestAllIterations = function () {
    $.each(pub.Projects, function (index, project) {
      TT.Ajax.start();
      $.get('/iterations', { project: project.id }, function (iterations) {
        iterations = JSON.parse(iterations).iteration;
        TT.API.addIterations(iterations);
        TT.Ajax.end();
        TT.View.drawStories();
      });
    });
    pub.updateColumnDimensions();
  };

  pub.onDomReady = function () {
    pub.initLayout();

    pub.View.drawColumns();
    pub.View.drawAccountNav();

    pub.updateColumnDimensions();
    $(window).resize(pub.updateColumnDimensions);

    TT.DragAndDrop.init();
    TT.Search.init();

    pub.requestProjectsAndIterations();
  };

  return pub;

}());
<|endoftext|>


<commit_before>var AWS = require('aws-sdk');
var co = require('co');
var fetch = require('node-fetch');
var moment = require('moment-timezone');

var cloudWatch = new AWS.CloudWatch({region: 'us-east-1'});
var yesterday = moment().tz('Asia/Tokyo').subtract(1, 'days');

exports.handler = function(evt, ctx, cb) {
  co(function *() {
    var res = yield cloudWatch.getMetricStatistics({
      Dimensions: [
        {
          Name: 'Currency',
          Value: 'USD'
        }
      ],
      EndTime: yesterday.endOf('day').unix(),
      MetricName: 'EstimatedCharges',
      Namespace: 'AWS/Billing',
      Period: 86400,
      StartTime: yesterday.startOf('day').unix(),
      Statistics: ['Sum']
    }).promise();

    if (res.Datapoints.length === 0) {
      return;
    }

    yield fetch(process.env.SLACK_WEBHOOK_URL, {method: 'post', body: JSON.stringify({
      username: 'aws-billing-bot',
      icon_emoji: ':aws:',
      text: '昨日までの AWS の利用料\n```EstimatedCharges: '
        + res.Datapoints[0].Sum + ' USD```'
    })});
  }).then(function() { cb(); })
    .catch(function(e) { cb(e); });
};
<commit_msg>use Maximum<commit_after>var AWS = require('aws-sdk');
var co = require('co');
var fetch = require('node-fetch');
var moment = require('moment-timezone');

var cloudWatch = new AWS.CloudWatch({region: 'us-east-1'});
var yesterday = moment().tz('Asia/Tokyo').subtract(1, 'days');

exports.handler = function(evt, ctx, cb) {
  co(function *() {
    var res = yield cloudWatch.getMetricStatistics({
      Dimensions: [
        {
          Name: 'Currency',
          Value: 'USD'
        }
      ],
      EndTime: yesterday.endOf('day').unix(),
      MetricName: 'EstimatedCharges',
      Namespace: 'AWS/Billing',
      Period: 86400,
      StartTime: yesterday.startOf('day').unix(),
      Statistics: ['Maximum']
    }).promise();

    if (res.Datapoints.length === 0) {
      return;
    }

    yield fetch(process.env.SLACK_WEBHOOK_URL, {method: 'post', body: JSON.stringify({
      username: 'aws-billing-bot',
      icon_emoji: ':aws:',
      text: '昨日までの AWS の利用料\n```EstimatedCharges: '
        + res.Datapoints[0].Maximum + ' USD```'
    })});
  }).then(function() { cb(); })
    .catch(function(e) { cb(e); });
};
<|endoftext|>


<commit_before>var redis = require('redis'),
    fs = require('fs'),
    path = require('path'),
    EventEmitter = require("events").EventEmitter,
    uuid = require('node-uuid');

var Thoonk = function(host, port) {
    EventEmitter.call(this);

    this.host = host || 'localhost';
    this.port = port || 6379;
    this.redis = redis.createClient(this.port, this.host);
    this.lredis = redis.createClient(this.port, this.host);
    this.ready = false;

    this.lredis.on('message', function(channel, msg) {
        msg = msg.toString();
        this.emit(channel, channel, msg);
    }.bind(this));

    this.lredis.on('subscribe', function(channel) {
        this.emit('subscribed.' + channel);
    }.bind(this));

    this.scripts = {};
    this.shas = {};

    this.instance = uuid();

    this.subscriptions = {};

    this.objects = {};
};

Thoonk.prototype = Object.create(EventEmitter.prototype);
Thoonk.prototype.constructor = Thoonk;

(function() {
    //continue extending Thoonk

    this.quit = function() {
        this.redis.quit();
        this.lredis.quit();
    };

    this.registerType = function(objname, theobject, callback, type) {
        if(typeof type == 'undefined' || type == 'object') {
            this.objects[objname] = function(name) {
                return new theobject(name, this);
            }.bind(this);
        } else if (type == 'interface') {
            this.objects[objname] = function() {
                return new theobject(this);
            }.bind(this);
        }
        var dir = theobject.prototype.scriptdir;
        this.scripts[theobject.prototype.objtype] = {};
        this.shas[theobject.prototype.objtype] = {}
        var curdir = fs.readdirSync(dir);
        curdir = curdir.filter(function(fname) { return fname.substr(-4) == '.lua'; });
        curdir.forEach(function(filename, fidx, curdir) {
            var last = (fidx + 1 == curdir.length);
            if(path.extname(filename) == '.lua') {
                var verbname = path.basename(filename).slice(0,-4);
                this.scripts[theobject.prototype.objtype][verbname] = fs.readFileSync(dir + '/' + filename).toString();
                this.redis.send_command('SCRIPT', ['LOAD', this.scripts[theobject.prototype.objtype][verbname]], function(err, reply) {
                    if(err) { console.log(verbname, err); }
                    this.shas[theobject.prototype.objtype][verbname] = reply.toString();
                    if(last) {
                        if(callback) {
                            callback(false);
                        }
                        this.emit('loaded.' + objname);
                    }
                }.bind(this));
            }
        }.bind(this));
    };

    this.registerInterface = function(objname, theobject, callback) {
        return this.registerType(objname, theobject, callback, 'interface');
    };

    this.create = function() {
    };

    this._runscript = function(objtype, scriptname, feedname, args, callback) {
        args = [this.shas[objtype][scriptname], '0', feedname].concat(args);
        this.redis.send_command('EVALSHA', args, callback);
    };

    this._runscriptsingle = function(objtype, scriptname, args, callback) {
        args = [this.shas[objtype][scriptname], '0'].concat(args);
        this.redis.send_command('EVALSHA', args, callback);
    };

    this.feed = function(name, config) {
        return new Feed(name);
    };

}).call(Thoonk.prototype);

var ThoonkBaseObject = function(name, thoonk) {
    EventEmitter.call(this);
    this.thoonk = thoonk;
    this.redis = this.thoonk.redis;
    this.name = name;
    //TODO: create feed if it doesn't exist
};

ThoonkBaseObject.prototype = Object.create(EventEmitter.prototype);
ThoonkBaseObject.constructor = ThoonkBaseObject;

(function() {

    this._build_event = function(eventtype) {
        return 'event.' + this.objtype + '.' + eventtype + ':' + this.name
    };

    this.handle_event = function(channel, msg) {
        //override this function in your object
    };

    this.init_subscribe = function(functions) {
        if(!this.thoonk.subscriptions.hasOwnProperty(this.name)) {
            this.thoonk.once('subscribed.' + this._build_event(this.subscribables[this.subscribables.length - 1]), function() {
                this.emit('subscribe_ready');
            }.bind(this));
            this.thoonk.subscriptions[this.name] = this.subscribables;
            for(var subscribable in this.subscribables) {
                this.thoonk.lredis.subscribe(this._build_event(this.subscribables[subscribable]));
            }
        }
        if(!this.subinitted) {
            for(var subscribable in this.subscribables) {
                this.thoonk.on(this._build_event(this.subscribables[subscribable]), this.handle_event.bind(this));
            }
            this.subinitted = true;
        }
    };

    this.runscript = function(scriptname, args, callback) {
        this.thoonk._runscript(this.objtype, scriptname, this.name, args, function(err, results) {
            if(err) {
                console.log(scriptname, err);
            } else {
                if(callback) {
                    callback.apply(this, results);
                }
            };
        }.bind(this));
    };

}).call(ThoonkBaseObject.prototype);

var ThoonkBaseInterface = function(thoonk) {
    EventEmitter.call(this);
    this.thoonk = thoonk;
    this.redis = this.thoonk.redis;
};

ThoonkBaseInterface.prototype = Object.create(EventEmitter.prototype);
ThoonkBaseInterface.constructor = ThoonkBaseInterface;

(function() {
    this._build_event = function(eventtype) {
        return 'event.' + this.objtype + '.' + eventtype;
    };

    this.handle_event = function(channel, msg) {
        //override this function in your object
    };

    this.init_subscribe = function(functions) {
        if(!this.thoonk.subscriptions.hasOwnProperty(this.name)) {
            this.thoonk.once('subscribed.' + this._build_event(this.subscribables[this.subscribables.length - 1]), function() {
                this.emit('subscribe_ready');
            }.bind(this));
            this.thoonk.subscriptions[this.name] = this.subscribables;
            for(var subscribable in this.subscribables) {
                this.thoonk.lredis.subscribe(this._build_event(this.subscribables[subscribable]));
            }
        }
        if(!this.subinitted) {
            for(var subscribable in this.subscribables) {
                this.thoonk.on(this._build_event(this.subscribables[subscribable]), this.handle_event.bind(this));
            }
            this.subinitted = true;
        }
    };

    this.runscript = function(scriptname, args, callback) {
        this.thoonk._runscriptsingle(this.objtype, scriptname, args, function(err, results) {
            if(err) {
                console.log(scriptname, err);
            } else {
                if(callback) {
                    callback.apply(this, results);
                }
            };
        }.bind(this));
    };
}).call(ThoonkBaseInterface.prototype);

exports.Thoonk = Thoonk;
exports.ThoonkBaseObject = ThoonkBaseObject;
exports.ThoonkBaseInterface = ThoonkBaseInterface;

exports.createClient = function(host, port) {
    return new Thoonk(host, port);
};
<commit_msg>Interfaces now get a subscription object back separately<commit_after>var redis = require('redis'),
    fs = require('fs'),
    path = require('path'),
    EventEmitter = require("events").EventEmitter,
    uuid = require('node-uuid');

var Thoonk = function(host, port) {
    EventEmitter.call(this);

    this.host = host || 'localhost';
    this.port = port || 6379;
    this.redis = redis.createClient(this.port, this.host);
    this.lredis = redis.createClient(this.port, this.host);
    this.ready = false;

    this.lredis.on('message', function(channel, msg) {
        msg = msg.toString();
        this.emit(channel, channel, msg);
    }.bind(this));

    this.lredis.on('subscribe', function(channel) {
        this.emit('subscribed.' + channel);
    }.bind(this));

    this.scripts = {};
    this.shas = {};

    this.instance = uuid();

    this.subscriptions = {};

    this.objects = {};
};

Thoonk.prototype = Object.create(EventEmitter.prototype);
Thoonk.prototype.constructor = Thoonk;

(function() {
    //continue extending Thoonk

    this.quit = function() {
        this.redis.quit();
        this.lredis.quit();
    };

    this.registerType = function(objname, theobject, callback, type) {
        if(typeof type == 'undefined' || type == 'object') {
            this.objects[objname] = function(name) {
                return new theobject(name, this);
            }.bind(this);
        } else if (type == 'interface') {
            this.objects[objname] = function() {
                return new theobject(this);
            }.bind(this);
        }
        var dir = theobject.prototype.scriptdir;
        this.scripts[theobject.prototype.objtype] = {};
        this.shas[theobject.prototype.objtype] = {}
        var curdir = fs.readdirSync(dir);
        curdir = curdir.filter(function(fname) { return fname.substr(-4) == '.lua'; });
        curdir.forEach(function(filename, fidx, curdir) {
            var last = (fidx + 1 == curdir.length);
            if(path.extname(filename) == '.lua') {
                var verbname = path.basename(filename).slice(0,-4);
                this.scripts[theobject.prototype.objtype][verbname] = fs.readFileSync(dir + '/' + filename).toString();
                this.redis.send_command('SCRIPT', ['LOAD', this.scripts[theobject.prototype.objtype][verbname]], function(err, reply) {
                    if(err) { console.log(verbname, err); }
                    this.shas[theobject.prototype.objtype][verbname] = reply.toString();
                    if(last) {
                        if(callback) {
                            callback(false);
                        }
                        this.emit('loaded.' + objname);
                    }
                }.bind(this));
            }
        }.bind(this));
    };

    this.registerInterface = function(objname, theobject, callback) {
        return this.registerType(objname, theobject, callback, 'interface');
    };

    this._runscript = function(objtype, scriptname, feedname, args, callback) {
        args = [this.shas[objtype][scriptname], '0', feedname].concat(args);
        this.redis.send_command('EVALSHA', args, callback);
    };

    this._runscriptsingle = function(objtype, scriptname, args, callback) {
        args = [this.shas[objtype][scriptname], '0'].concat(args);
        this.redis.send_command('EVALSHA', args, callback);
    };

    this.feed = function(name, config) {
        return new Feed(name);
    };

}).call(Thoonk.prototype);

var Subscription = function(thoonk, objtype, instance, subscribables, event_handler) {
    EventEmitter.call(this);
    this.thoonk = thoonk;
    this.instance = instance;
    this.objtype = objtype;
    this.sub = this.objtype + '::' + this.instance;
    this.subscribables = subscribables;
    this.init_subscribe(event_handler);
};

Subscription.prototype = Object.create(EventEmitter.prototype);
Subscription.prototype.constructor = Subscription;

(function() {

    this._build_event = function(eventtype) {
        if(this.instance) {
            return 'event.' + this.objtype + '.' + eventtype + ':' + this.instance;
        } else {
            return 'event.' + this.objtype + '.' + eventtype;
        }
    };

    this.init_subscribe = function(event_handler) {
        if(!this.thoonk.subscriptions.hasOwnProperty(this.sub)) {
            this.thoonk.once('subscribed.' + this._build_event(this.subscribables[this.subscribables.length - 1]), function() {
                this.emit('subscribe_ready');
            }.bind(this));
            this.thoonk.subscriptions[this.sub] = this.subscribables;
            for(var subscribable in this.subscribables) {
                this.thoonk.lredis.subscribe(this._build_event(this.subscribables[subscribable]));
            }
        }
        if(!this.subinitted) {
            for(var subscribable in this.subscribables) {
                if(typeof event_handler != "undefined") {
                    this.thoonk.on(this._build_event(this.subscribables[subscribable]), event_handler);
                }
                this.thoonk.on(this._build_event(this.subscribables[subscribable]), this.handle_event.bind(this));
            }
            this.subinitted = true;
        }
    };

    this.handle_event = function(channel, msg) {
        this.emit(channel, msg);
        this.emit('all', channel, msg);
    };

}).call(Subscription.prototype);

var ThoonkBaseObject = function(name, thoonk) {
    EventEmitter.call(this);
    this.thoonk = thoonk;
    this.redis = this.thoonk.redis;
    this.name = name;
    this.subscription = null;
    //TODO: create feed if it doesn't exist
};

ThoonkBaseObject.prototype = Object.create(EventEmitter.prototype);
ThoonkBaseObject.constructor = ThoonkBaseObject;

(function() {

    this.handle_event = function(channel, msg) {
        //override this function in your object
    };

    this.init_subscribe = function() {
        this.subscription = new Subscription(this.thoonk, this.objtype, this.name, this.subscribables, this.handle_event.bind(this));
        this.subscription.once("subscribe_ready", function() {
            this.emit("subscribe_ready");
        }.bind(this));
    };

    this.runscript = function(scriptname, args, callback) {
        this.thoonk._runscript(this.objtype, scriptname, this.name, args, function(err, results) {
            results =results.concat(['crap']);
            if(err) {
                console.log(scriptname, err);
            } else {
                if(callback) {
                    callback.apply(this, results);
                }
            };
        }.bind(this));
    };

}).call(ThoonkBaseObject.prototype);

var ThoonkBaseInterface = function(thoonk) {
    EventEmitter.call(this);
    this.thoonk = thoonk;
    this.redis = this.thoonk.redis;
};

ThoonkBaseInterface.prototype = Object.create(EventEmitter.prototype);
ThoonkBaseInterface.constructor = ThoonkBaseInterface;

(function() {
    this.handle_event = function(channel, msg) {
        //override this function in your object
    };

    this.getEmitter = function(event_handler) {
        return new Subscription(this.thoonk, this.objtype, undefined, this.subscribables, event_handler);
    };

    this.runscript = function(scriptname, args, callback) {
        this.thoonk._runscriptsingle(this.objtype, scriptname, args, function(err, results) {
            if(err) {
                console.log(scriptname, err);
            } else {
                if(callback) {
                    callback.apply(this, results);
                }
            };
        }.bind(this));
    };
}).call(ThoonkBaseInterface.prototype);

exports.Thoonk = Thoonk;
exports.ThoonkBaseObject = ThoonkBaseObject;
exports.ThoonkBaseInterface = ThoonkBaseInterface;

exports.createClient = function(host, port) {
    return new Thoonk(host, port);
};
<|endoftext|>


<commit_before>
function createTransactionFactory() {

    const _createTransaction = function(transactionId, type, transactionHandler) {

        let _ready = false;

        let _processing = false;

        let _failed = false;

        let _errors = [];

        let _validationErrors = null;

        const _handleTransactionReady = function() {
            _processing = false;
            _failed = false;
            _errors = [];
            _validationErrors = null;
            _ready = true;
        },

        const _handleTransactionCanceled = function() {
            _processing = false;
            _failed = false;
        },

        const _handleTransactionFailed = function(errors, meta) {
            _processing = false;
            _failed = true;
            _errors = meta.errors.slice(0);
            _validationErrors = {};
            if (meta.hasOwnProperty('validationErrors')) {
                for (var key in meta.validationErrors) {
                    _validationErrors[key] = meta.validationErrors[key];
                }
            }
        },

        return {

            transactionId: function() {
                return transactionId;
            },

            type: function() {
                return type;
            },

            start: function() {
                if (!this.processing()) {
                    transactionHandler.start({
                        onReady: _handleTransactionReady,
                        onFailed: _handleTransactionFailed
                    })
                }
            },

            cancel: function() {
                if (this.processing()) {
                    transactionHandler.cancel({
                        onCanceled: _handleTransactionCanceled
                    })
                }
            },

            ready: function() {
                return _ready;
            },

            processing: function() {
                return _processing;
            },

            failed: function() {
                return _failed;
            },

            hasErrors: function() {
                return (_errors.length > 0);
            },

            errors: function() {
                return _errors;
            },

            hasValidationErrors: function() {
                return (Object.keys(_validationErrors).length > 0);
            },

            validationErrors: function() {
                return _validationErrors;
            }
        }
    }

    return {

        createTransaction: _createTransaction,

    };

}

export default createTransactionFactory;
<commit_msg>better transaction state<commit_after>
function createTransactionFactory() {

    const _createTransaction = function(transactionId, type, transactionHandler) {

        let _ready = false;

        let _processing = false;

        let _failed = false;

        let _errors = [];

        let _validationErrors = null;

        const _handleTransactionReady = function() {
            _ready = true;
            _processing = false;
            _failed = false;
            _errors = [];
            _validationErrors = null;
            _ready = true;
        },

        const _handleTransactionCanceled = function() {
            _ready = false;
            _processing = false;
            _failed = false;
        },

        const _handleTransactionFailed = function(errors, meta) {
            _ready = true;
            _processing = false;
            _failed = true;
            _errors = meta.errors.slice(0);
            _validationErrors = {};
            if (meta.hasOwnProperty('validationErrors')) {
                for (var key in meta.validationErrors) {
                    _validationErrors[key] = meta.validationErrors[key];
                }
            }
        },

        return {

            transactionId: function() {
                return transactionId;
            },

            type: function() {
                return type;
            },

            start: function() {
                if (!this.processing()) {
                    _ready = false;
                    _processing = true;
                    _errors = [];
                    _fieldErrors = {};
                    transactionHandler.start({
                        onReady: _handleTransactionReady,
                        onFailed: _handleTransactionFailed
                    });
                }
            },

            cancel: function() {
                if (this.processing()) {
                    transactionHandler.cancel({
                        onCanceled: _handleTransactionCanceled
                    });
                }
            },

            ready: function() {
                return _ready;
            },

            processing: function() {
                return _processing;
            },

            failed: function() {
                return _failed;
            },

            hasErrors: function() {
                return (_errors.length > 0);
            },

            errors: function() {
                return _errors;
            },

            hasValidationErrors: function() {
                return (Object.keys(_validationErrors).length > 0);
            },

            validationErrors: function() {
                return _validationErrors;
            }
        }
    }

    return {

        createTransaction: _createTransaction,

    };

}

export default createTransactionFactory;
<|endoftext|>


<commit_before>/*jshint worker: true */
import Geo from '../geo';
import {MethodNotImplemented} from '../utils/errors';
import Utils from '../utils/utils';
import * as URLs from '../utils/urls';
import log from '../utils/log';

export default class DataSource {

    constructor (config, sources) {
        this.config = config; // save original config
        this.sources = sources; // full set of data sources TODO: centralize these like textures?
        this.id = config.id;
        this.name = config.name;
        this.pad_scale = config.pad_scale || 0.0001; // scale tile up by small factor to cover seams
        this.default_winding = null; // winding order will adapt to data source
        this.rasters = []; // attached raster tile sources
        if (Array.isArray(config.rasters)) { // copy unique set of raster sources
            config.rasters.forEach(r => {
                if (this.rasters.indexOf(r) === -1) {
                    this.rasters.push(r);
                }
            });
        }

        // Optional function to preprocess source data
        this.preprocess = config.preprocess;
        if (typeof this.preprocess === 'function') {
            this.preprocess.bind(this);
        }

        // Optional function to transform source data
        this.transform = config.transform;
        if (typeof this.transform === 'function') {
            this.transform.bind(this);
        }

        // Optional additional data to pass to the transform function
        this.extra_data = config.extra_data;

        // Optional additional scripts made available to the transform function
        // NOTE: these are loaded alongside the library when the workers are instantiated
        this.scripts = config.scripts;

        // overzoom will apply for zooms higher than this
        this.max_zoom = (config.max_zoom != null) ? config.max_zoom : Geo.default_source_max_zoom;

        // set a custom extra overzoom adjustment factor to load consistently lower zoom levels
        // than the current map zoom level – eg a zoom_offset of 1 would load z3 data at z4
        this.zoom_offset = (config.zoom_offset != null) ? config.zoom_offset : 0;

        this.setTileSize(config.tile_size);
        // factor in zoom_bias to account for tile sizes > than 256px
        this.max_coord_zoom = this.max_zoom + this.zoom_bias;

        // no tiles will be requested or displayed outside of these min/max values
        this.min_display_zoom = (config.min_display_zoom != null) ? config.min_display_zoom : 0;
        this.max_display_zoom = (config.max_display_zoom != null) ? config.max_display_zoom : null;
    }

    // Create a tile source by type, factory-style
    static create (source, sources) {
        if (DataSource.types[source.type]) {
            return new DataSource.types[source.type](source, sources);
        }
    }

    // Check if a data source definition changed
    static changed (source, prev_source) {
        if (!source || !prev_source) {
            return true;
        }

        let cur = Object.assign({}, source.config, { id: null }); // null out ids since we don't want to compare them
        let prev = Object.assign({}, prev_source.config, { id: null });

        return JSON.stringify(cur) !== JSON.stringify(prev);
    }

    // Mercator projection
    static projectData (source) {
        var timer = +new Date();
        for (var t in source.layers) {
            var num_features = source.layers[t].features.length;
            for (var f=0; f < num_features; f++) {
                var feature = source.layers[t].features[f];
                Geo.transformGeometry(feature.geometry, coord => {
                    var [x, y] = Geo.latLngToMeters(coord);
                    coord[0] = x;
                    coord[1] = y;
                });
            }
        }

        if (source.debug !== undefined) {
            source.debug.projection = +new Date() - timer;
        }
    }

    /**
     Re-scale geometries within each source to internal tile units
    */
    static scaleData (source, {coords: {z}, min, max}) {
        let units_per_meter = Geo.unitsPerMeter(z);
        for (var t in source.layers) {
            var num_features = source.layers[t].features.length;
            for (var f=0; f < num_features; f++) {
                var feature = source.layers[t].features[f];
                Geo.transformGeometry(feature.geometry, coord => {
                    coord[0] = (coord[0] - min.x) * units_per_meter;
                    coord[1] = (coord[1] - min.y) * units_per_meter * -1; // flip coords positive
                });
            }
        }
    }

    load(dest) {
        dest.source_data = {};
        dest.source_data.layers = {};
        dest.pad_scale = this.pad_scale;
        dest.rasters = [...this.rasters]; // copy list of rasters to load for tile

        return this._load(dest).then((dest) => {
            // Post-processing
            for (let layer in dest.source_data.layers) {
                let data = dest.source_data.layers[layer];
                if (data && data.features) {
                    data.features.forEach(feature => {
                        Geo.transformGeometry(feature.geometry, coord => {
                            // Flip Y coords
                            coord[1] = -coord[1];

                            // Slightly scale up tile to cover seams
                            if (this.pad_scale) {
                                coord[0] = Math.round(coord[0] * (1 + this.pad_scale) - (Geo.tile_scale * this.pad_scale/2));
                                coord[1] = Math.round(coord[1] * (1 + this.pad_scale) - (Geo.tile_scale * this.pad_scale/2));
                            }
                        });

                        // Use first encountered polygon winding order as default for data source
                        this.updateDefaultWinding(feature.geometry);
                    });
                }
            }

            dest.default_winding = this.default_winding || 'CCW';
            return dest;
        });
    }

    // Sub-classes must implement
    _load(dest) {
        throw new MethodNotImplemented('_load');
    }

    // Set the internal tile size in pixels, e.g. '256px' (default), '512px', etc.
    // Must be a power of 2, and greater than or equal to 256
    setTileSize (tile_size) {
        this.tile_size = tile_size || 256;
        if (typeof this.tile_size !== 'number' || this.tile_size < 256 || !Utils.isPowerOf2(this.tile_size)) {
            log({ level: 'warn', once: true },
                `Data source '${this.name}': 'tile_size' parameter must be a number that is a power of 2 greater than or equal to 256, but was '${tile_size}'`);
            this.tile_size = 256;
        }

        // # of zoom levels bigger than 256px tiles - 8 in place of log2(256)
        // Many Tangram functions assume 256px tiles, this factor adjusts for the
        // case of bigger tile sizes - eg 512px tiles are 1 zoom level bigger,
        // 1024px tiles are 2 levels bigger
        this.zoom_bias = Math.log2(this.tile_size) - 8 + this.tile_adjust;
    }

    // Infer winding for data source from first ring of provided geometry
    updateDefaultWinding (geom) {
        if (this.default_winding == null) {
            if (geom.type === 'Polygon') {
                this.default_winding = Geo.ringWinding(geom.coordinates[0]);
            }
            else if (geom.type === 'MultiPolygon') {
                this.default_winding = Geo.ringWinding(geom.coordinates[0][0]);
            }
        }
        return this.default_winding;
    }

    // All data sources support a min zoom, tiled sources can subclass for more specific limits (e.g. bounding box)
    includesTile (coords, style_zoom) {
        // Limit by this data source
        if (coords.z < this.min_display_zoom || (this.max_display_zoom != null && style_zoom > this.max_display_zoom)) {
            return false;
        }

        // Limit by any dependent raster sources
        for (let r=0; r < this.rasters.length; r++) {
            const source_name = this.rasters[r];
            if (this.sources[source_name] &&
                this.sources[source_name] !== this &&
                !this.sources[source_name].includesTile(coords, coords.z)) {
                return false;
            }
        }

        return true;
    }

    // Register a new data source type, under a type name
    static register(type_class, type_name) {
        if (!type_class || !type_name) {
            return;
        }

        DataSource.types[type_name] = type_class;
    }

}

DataSource.types = {}; // set of supported data source classes, referenced by type name

/*** Generic network loading source - abstract class ***/

let network_request_id = 0; // used to namespace URL requests

export class NetworkSource extends DataSource {

    constructor (source, sources) {
        super(source, sources);
        this.response_type = ""; // use to set explicit XHR type

        if (typeof source.url !== 'string') {
            throw Error('Network data source must provide a string `url` property');
        }

        // Add extra URL params, and warn on duplicates
        let [url, dupes] = URLs.addParamsToURL(source.url, source.url_params);
        this.url = url;
        dupes.forEach(([param, value]) => {
            log({ level: 'warn', once: true },
                `Data source '${this.name}': parameter '${param}' already present in URL '${source.url}', ` +
                `skipping value '${param}=${value}' specified in 'url_params'`);
        });

        // Optional HTTP request headers to send
        if (source.request_headers && typeof source.request_headers === 'object') {
            this.request_headers = source.request_headers;
        }
    }

    _load (dest) {
        let url = this.formatUrl(this.url, dest);

        let source_data = dest.source_data;
        source_data.url = url;
        dest.debug = dest.debug || {};
        dest.debug.network = +new Date();

        return new Promise((resolve, reject) => {
            source_data.error = null;
            // For testing network errors
            // var promise = Utils.io(url, 60 * 100, this.response_type);
            // if (Math.random() < .7) {
            //     promise = Promise.reject(Error('fake data source error'));
            // }
            // promise.then((body) => {

            let request_id = (network_request_id++) + '-' + url;
            let promise = Utils.io(url, 60 * 1000, this.response_type, 'GET', this.request_headers, request_id);
            source_data.request_id = request_id;

            promise.then((body) => {
                dest.debug.response_size = body.length || body.byteLength;
                dest.debug.network = +new Date() - dest.debug.network;
                dest.debug.parsing = +new Date();

                // Apply optional data transform on raw network response
                if (typeof this.preprocess === 'function') {
                    body = this.preprocess(body);
                }

                // Return data immediately, or after user-returned promise resolves
                body = (body instanceof Promise) ? body : Promise.resolve(body);
                body.then(body => {
                    this.parseSourceData(dest, source_data, body);
                    dest.debug.parsing = +new Date() - dest.debug.parsing;
                    resolve(dest);
                });
            }).catch((error) => {
                source_data.error = error.stack;
                resolve(dest); // resolve request but pass along error
            });
        });
    }

    // Sub-classes must implement:

    formatUrl (url_template, dest) {
        throw new MethodNotImplemented('formatUrl');
    }

    parseSourceData (dest, source, reponse) {
        throw new MethodNotImplemented('parseSourceData');
    }
}

/*** Generic network tile loading - abstract class ***/

export class NetworkTileSource extends NetworkSource {

    constructor (source, sources) {
        super(source, sources);

        this.tiled = true;
        this.parseBounds(source);

        // indicates if source should build geometry tiles, enabled for sources referenced in the scene's layers,
        // and left disabled for sources that are never referenced, or only used as raster textures
        this.builds_geometry_tiles = false;

        this.tms = (source.tms === true); // optionally flip tile coords for TMS

        // optional list of subdomains to round-robin through
        if (this.url.search('{s}') > -1) {
            if (Array.isArray(source.url_subdomains) && source.url_subdomains.length > 0) {
                this.url_subdomains = source.url_subdomains;
                this.next_url_subdomain = 0;
            }
            else {
                log({ level: 'warn', once: true },
                    `Data source '${this.name}': source URL includes '\{s\}' subdomain marker ('${this.url}'), but no subdomains ` +
                    `were specified in 'url_subdomains' parameter`);
            }
        }
    }

    // Get bounds from source config parameters
    parseBounds (source) {
        if (Array.isArray(source.bounds) && source.bounds.length === 4) {
            this.bounds = source.bounds;
            let [w, s, e, n] = this.bounds;
            this.bounds_meters = {
                min: Geo.latLngToMeters([w, n]),
                max: Geo.latLngToMeters([e, s]),
            };
            this.bounds_tiles = { min: {}, max: {} }; // max tile bounds per zoom (lazily evaluated)
        }
    }

    // Returns false if tile is outside data source's bounds, true if within
    checkBounds (coords) {
        // Check tile bounds
        if (this.bounds) {
            coords = Geo.wrapTile(coords, { x: true });

            let min = this.bounds_tiles.min[coords.z];
            if (!min) {
                min = this.bounds_tiles.min[coords.z] = Geo.tileForMeters(this.bounds_meters.min, coords.z);
            }

            let max = this.bounds_tiles.max[coords.z];
            if (!max) {
                max = this.bounds_tiles.max[coords.z] = Geo.tileForMeters(this.bounds_meters.max, coords.z);
            }

            if (coords.x < min.x || coords.x > max.x ||
                coords.y < min.y || coords.y > max.y) {
                return false;
            }
        }
        return true;
    }

    includesTile (coords, style_zoom) {
        if (!super.includesTile(coords, style_zoom)) {
            return false;
        }

        // Check tile bounds
        if (!this.checkBounds(coords)) {
            return false;
        }
        return true;
    }

    formatUrl(url_template, tile) {
        let coords = Geo.wrapTile(tile.coords, { x: true });

        if (this.tms) {
            coords.y = Math.pow(2, coords.z) - 1 - coords.y; // optionally flip tile coords for TMS
        }

        let url = url_template.replace('{x}', coords.x).replace('{y}', coords.y).replace('{z}', coords.z);

        if (this.url_subdomains != null) {
            url = url.replace('{s}', this.url_subdomains[this.next_url_subdomain]);
            this.next_url_subdomain = (this.next_url_subdomain + 1) % this.url_subdomains.length;
        }
        return url;
    }

    // Checks for the x/y/z tile pattern in URL template
    urlHasTilePattern(url) {
        return url &&
            url.search('{x}') > -1 &&
            url.search('{y}') > -1 &&
            url.search('{z}') > -1;
    }

}
<commit_msg>rename misnamed parameter<commit_after>/*jshint worker: true */
import Geo from '../geo';
import {MethodNotImplemented} from '../utils/errors';
import Utils from '../utils/utils';
import * as URLs from '../utils/urls';
import log from '../utils/log';

export default class DataSource {

    constructor (config, sources) {
        this.config = config; // save original config
        this.sources = sources; // full set of data sources TODO: centralize these like textures?
        this.id = config.id;
        this.name = config.name;
        this.pad_scale = config.pad_scale || 0.0001; // scale tile up by small factor to cover seams
        this.default_winding = null; // winding order will adapt to data source
        this.rasters = []; // attached raster tile sources
        if (Array.isArray(config.rasters)) { // copy unique set of raster sources
            config.rasters.forEach(r => {
                if (this.rasters.indexOf(r) === -1) {
                    this.rasters.push(r);
                }
            });
        }

        // Optional function to preprocess source data
        this.preprocess = config.preprocess;
        if (typeof this.preprocess === 'function') {
            this.preprocess.bind(this);
        }

        // Optional function to transform source data
        this.transform = config.transform;
        if (typeof this.transform === 'function') {
            this.transform.bind(this);
        }

        // Optional additional data to pass to the transform function
        this.extra_data = config.extra_data;

        // Optional additional scripts made available to the transform function
        // NOTE: these are loaded alongside the library when the workers are instantiated
        this.scripts = config.scripts;

        // overzoom will apply for zooms higher than this
        this.max_zoom = (config.max_zoom != null) ? config.max_zoom : Geo.default_source_max_zoom;

        // set a custom extra overzoom adjustment factor to load consistently lower zoom levels
        // than the current map zoom level – eg a zoom_offset of 1 would load z3 data at z4
        this.zoom_offset = (config.zoom_offset != null) ? config.zoom_offset : 0;

        this.setTileSize(config.tile_size);
        // factor in zoom_bias to account for tile sizes > than 256px
        this.max_coord_zoom = this.max_zoom + this.zoom_bias;

        // no tiles will be requested or displayed outside of these min/max values
        this.min_display_zoom = (config.min_display_zoom != null) ? config.min_display_zoom : 0;
        this.max_display_zoom = (config.max_display_zoom != null) ? config.max_display_zoom : null;
    }

    // Create a tile source by type, factory-style
    static create (source, sources) {
        if (DataSource.types[source.type]) {
            return new DataSource.types[source.type](source, sources);
        }
    }

    // Check if a data source definition changed
    static changed (source, prev_source) {
        if (!source || !prev_source) {
            return true;
        }

        let cur = Object.assign({}, source.config, { id: null }); // null out ids since we don't want to compare them
        let prev = Object.assign({}, prev_source.config, { id: null });

        return JSON.stringify(cur) !== JSON.stringify(prev);
    }

    // Mercator projection
    static projectData (source) {
        var timer = +new Date();
        for (var t in source.layers) {
            var num_features = source.layers[t].features.length;
            for (var f=0; f < num_features; f++) {
                var feature = source.layers[t].features[f];
                Geo.transformGeometry(feature.geometry, coord => {
                    var [x, y] = Geo.latLngToMeters(coord);
                    coord[0] = x;
                    coord[1] = y;
                });
            }
        }

        if (source.debug !== undefined) {
            source.debug.projection = +new Date() - timer;
        }
    }

    /**
     Re-scale geometries within each source to internal tile units
    */
    static scaleData (source, {coords: {z}, min, max}) {
        let units_per_meter = Geo.unitsPerMeter(z);
        for (var t in source.layers) {
            var num_features = source.layers[t].features.length;
            for (var f=0; f < num_features; f++) {
                var feature = source.layers[t].features[f];
                Geo.transformGeometry(feature.geometry, coord => {
                    coord[0] = (coord[0] - min.x) * units_per_meter;
                    coord[1] = (coord[1] - min.y) * units_per_meter * -1; // flip coords positive
                });
            }
        }
    }

    load(dest) {
        dest.source_data = {};
        dest.source_data.layers = {};
        dest.pad_scale = this.pad_scale;
        dest.rasters = [...this.rasters]; // copy list of rasters to load for tile

        return this._load(dest).then((dest) => {
            // Post-processing
            for (let layer in dest.source_data.layers) {
                let data = dest.source_data.layers[layer];
                if (data && data.features) {
                    data.features.forEach(feature => {
                        Geo.transformGeometry(feature.geometry, coord => {
                            // Flip Y coords
                            coord[1] = -coord[1];

                            // Slightly scale up tile to cover seams
                            if (this.pad_scale) {
                                coord[0] = Math.round(coord[0] * (1 + this.pad_scale) - (Geo.tile_scale * this.pad_scale/2));
                                coord[1] = Math.round(coord[1] * (1 + this.pad_scale) - (Geo.tile_scale * this.pad_scale/2));
                            }
                        });

                        // Use first encountered polygon winding order as default for data source
                        this.updateDefaultWinding(feature.geometry);
                    });
                }
            }

            dest.default_winding = this.default_winding || 'CCW';
            return dest;
        });
    }

    // Sub-classes must implement
    _load(dest) {
        throw new MethodNotImplemented('_load');
    }

    // Set the internal tile size in pixels, e.g. '256px' (default), '512px', etc.
    // Must be a power of 2, and greater than or equal to 256
    setTileSize (tile_size) {
        this.tile_size = tile_size || 256;
        if (typeof this.tile_size !== 'number' || this.tile_size < 256 || !Utils.isPowerOf2(this.tile_size)) {
            log({ level: 'warn', once: true },
                `Data source '${this.name}': 'tile_size' parameter must be a number that is a power of 2 greater than or equal to 256, but was '${tile_size}'`);
            this.tile_size = 256;
        }

        // # of zoom levels bigger than 256px tiles - 8 in place of log2(256)
        // Many Tangram functions assume 256px tiles, this factor adjusts for the
        // case of bigger tile sizes - eg 512px tiles are 1 zoom level bigger,
        // 1024px tiles are 2 levels bigger
        this.zoom_bias = Math.log2(this.tile_size) - 8 + this.zoom_offset;
    }

    // Infer winding for data source from first ring of provided geometry
    updateDefaultWinding (geom) {
        if (this.default_winding == null) {
            if (geom.type === 'Polygon') {
                this.default_winding = Geo.ringWinding(geom.coordinates[0]);
            }
            else if (geom.type === 'MultiPolygon') {
                this.default_winding = Geo.ringWinding(geom.coordinates[0][0]);
            }
        }
        return this.default_winding;
    }

    // All data sources support a min zoom, tiled sources can subclass for more specific limits (e.g. bounding box)
    includesTile (coords, style_zoom) {
        // Limit by this data source
        if (coords.z < this.min_display_zoom || (this.max_display_zoom != null && style_zoom > this.max_display_zoom)) {
            return false;
        }

        // Limit by any dependent raster sources
        for (let r=0; r < this.rasters.length; r++) {
            const source_name = this.rasters[r];
            if (this.sources[source_name] &&
                this.sources[source_name] !== this &&
                !this.sources[source_name].includesTile(coords, coords.z)) {
                return false;
            }
        }

        return true;
    }

    // Register a new data source type, under a type name
    static register(type_class, type_name) {
        if (!type_class || !type_name) {
            return;
        }

        DataSource.types[type_name] = type_class;
    }

}

DataSource.types = {}; // set of supported data source classes, referenced by type name

/*** Generic network loading source - abstract class ***/

let network_request_id = 0; // used to namespace URL requests

export class NetworkSource extends DataSource {

    constructor (source, sources) {
        super(source, sources);
        this.response_type = ""; // use to set explicit XHR type

        if (typeof source.url !== 'string') {
            throw Error('Network data source must provide a string `url` property');
        }

        // Add extra URL params, and warn on duplicates
        let [url, dupes] = URLs.addParamsToURL(source.url, source.url_params);
        this.url = url;
        dupes.forEach(([param, value]) => {
            log({ level: 'warn', once: true },
                `Data source '${this.name}': parameter '${param}' already present in URL '${source.url}', ` +
                `skipping value '${param}=${value}' specified in 'url_params'`);
        });

        // Optional HTTP request headers to send
        if (source.request_headers && typeof source.request_headers === 'object') {
            this.request_headers = source.request_headers;
        }
    }

    _load (dest) {
        let url = this.formatUrl(this.url, dest);

        let source_data = dest.source_data;
        source_data.url = url;
        dest.debug = dest.debug || {};
        dest.debug.network = +new Date();

        return new Promise((resolve, reject) => {
            source_data.error = null;
            // For testing network errors
            // var promise = Utils.io(url, 60 * 100, this.response_type);
            // if (Math.random() < .7) {
            //     promise = Promise.reject(Error('fake data source error'));
            // }
            // promise.then((body) => {

            let request_id = (network_request_id++) + '-' + url;
            let promise = Utils.io(url, 60 * 1000, this.response_type, 'GET', this.request_headers, request_id);
            source_data.request_id = request_id;

            promise.then((body) => {
                dest.debug.response_size = body.length || body.byteLength;
                dest.debug.network = +new Date() - dest.debug.network;
                dest.debug.parsing = +new Date();

                // Apply optional data transform on raw network response
                if (typeof this.preprocess === 'function') {
                    body = this.preprocess(body);
                }

                // Return data immediately, or after user-returned promise resolves
                body = (body instanceof Promise) ? body : Promise.resolve(body);
                body.then(body => {
                    this.parseSourceData(dest, source_data, body);
                    dest.debug.parsing = +new Date() - dest.debug.parsing;
                    resolve(dest);
                });
            }).catch((error) => {
                source_data.error = error.stack;
                resolve(dest); // resolve request but pass along error
            });
        });
    }

    // Sub-classes must implement:

    formatUrl (url_template, dest) {
        throw new MethodNotImplemented('formatUrl');
    }

    parseSourceData (dest, source, reponse) {
        throw new MethodNotImplemented('parseSourceData');
    }
}

/*** Generic network tile loading - abstract class ***/

export class NetworkTileSource extends NetworkSource {

    constructor (source, sources) {
        super(source, sources);

        this.tiled = true;
        this.parseBounds(source);

        // indicates if source should build geometry tiles, enabled for sources referenced in the scene's layers,
        // and left disabled for sources that are never referenced, or only used as raster textures
        this.builds_geometry_tiles = false;

        this.tms = (source.tms === true); // optionally flip tile coords for TMS

        // optional list of subdomains to round-robin through
        if (this.url.search('{s}') > -1) {
            if (Array.isArray(source.url_subdomains) && source.url_subdomains.length > 0) {
                this.url_subdomains = source.url_subdomains;
                this.next_url_subdomain = 0;
            }
            else {
                log({ level: 'warn', once: true },
                    `Data source '${this.name}': source URL includes '\{s\}' subdomain marker ('${this.url}'), but no subdomains ` +
                    `were specified in 'url_subdomains' parameter`);
            }
        }
    }

    // Get bounds from source config parameters
    parseBounds (source) {
        if (Array.isArray(source.bounds) && source.bounds.length === 4) {
            this.bounds = source.bounds;
            let [w, s, e, n] = this.bounds;
            this.bounds_meters = {
                min: Geo.latLngToMeters([w, n]),
                max: Geo.latLngToMeters([e, s]),
            };
            this.bounds_tiles = { min: {}, max: {} }; // max tile bounds per zoom (lazily evaluated)
        }
    }

    // Returns false if tile is outside data source's bounds, true if within
    checkBounds (coords) {
        // Check tile bounds
        if (this.bounds) {
            coords = Geo.wrapTile(coords, { x: true });

            let min = this.bounds_tiles.min[coords.z];
            if (!min) {
                min = this.bounds_tiles.min[coords.z] = Geo.tileForMeters(this.bounds_meters.min, coords.z);
            }

            let max = this.bounds_tiles.max[coords.z];
            if (!max) {
                max = this.bounds_tiles.max[coords.z] = Geo.tileForMeters(this.bounds_meters.max, coords.z);
            }

            if (coords.x < min.x || coords.x > max.x ||
                coords.y < min.y || coords.y > max.y) {
                return false;
            }
        }
        return true;
    }

    includesTile (coords, style_zoom) {
        if (!super.includesTile(coords, style_zoom)) {
            return false;
        }

        // Check tile bounds
        if (!this.checkBounds(coords)) {
            return false;
        }
        return true;
    }

    formatUrl(url_template, tile) {
        let coords = Geo.wrapTile(tile.coords, { x: true });

        if (this.tms) {
            coords.y = Math.pow(2, coords.z) - 1 - coords.y; // optionally flip tile coords for TMS
        }

        let url = url_template.replace('{x}', coords.x).replace('{y}', coords.y).replace('{z}', coords.z);

        if (this.url_subdomains != null) {
            url = url.replace('{s}', this.url_subdomains[this.next_url_subdomain]);
            this.next_url_subdomain = (this.next_url_subdomain + 1) % this.url_subdomains.length;
        }
        return url;
    }

    // Checks for the x/y/z tile pattern in URL template
    urlHasTilePattern(url) {
        return url &&
            url.search('{x}') > -1 &&
            url.search('{y}') > -1 &&
            url.search('{z}') > -1;
    }

}
<|endoftext|>


<commit_before>var express = require('../')
  , request = require('./support/http')
  , assert = require('assert');

describe('app.router', function(){
  describe('methods supported', function(){
    express.methods.forEach(function(method){
      it('should include ' + method.toUpperCase(), function(done){
        var app = express();
        var calls = [];

        app[method]('/foo', function(req, res){
          if ('head' == method) {
            res.end();
          } else {
            res.end(method);
          }
        });

        request(app)
        [method]('/foo')
        .expect('head' == method ? '' : method, done);
      })
    });
  })

  it('should be .use()able', function(done){
    var app = express();

    var calls = [];

    app.use(function(req, res, next){
      calls.push('before');
      next();
    });

    app.use(app.router);

    app.use(function(req, res, next){
      calls.push('after');
      res.end();
    });

    app.get('/', function(req, res, next){
      calls.push('GET /')
      next();
    });

    request(app)
    .get('/')
    .end(function(res){
      calls.should.eql(['before', 'GET /', 'after'])
      done();
    })
  })

  it('should be auto .use()d on the first app.VERB() call', function(done){
    var app = express();

    var calls = [];

    app.use(function(req, res, next){
      calls.push('before');
      next();
    });

    app.get('/', function(req, res, next){
      calls.push('GET /')
      next();
    });

    app.use(function(req, res, next){
      calls.push('after');
      res.end();
    });

    request(app)
    .get('/')
    .end(function(res){
      calls.should.eql(['before', 'GET /', 'after'])
      done();
    })
  })

  describe('when given a regexp', function(){
    it('should match the pathname only', function(done){
      var app = express();

      app.get(/^\/user\/[0-9]+$/, function(req, res){
        res.end('user');
      });

      request(app)
      .get('/user/12?foo=bar')
      .expect('user', done);
    })

    it('should populate req.params with the captures', function(done){
      var app = express();

      app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function(req, res){
        var id = req.params.shift()
          , op = req.params.shift();
        res.end(op + 'ing user ' + id);
      });

      request(app)
      .get('/user/10/edit')
      .expect('editing user 10', done);
    })
  })

  describe('when given an array', function(){
  	it('should match all paths in the array', function(done){
			var app = express();

			app.get(['/one', '/two'], function(req, res){
				res.end('works');
			});

			request(app)
			.get('/one')
			.expect('works', function() {
				request(app)
				.get('/two')
				.expect('works', done);
			});
		})
	})

  describe('case sensitivity', function(){
    it('should be disabled by default', function(done){
      var app = express();

      app.get('/user', function(req, res){
        res.end('tj');
      });

      request(app)
      .get('/USER')
      .expect('tj', done);
    })

    describe('when "case sensitive routing" is enabled', function(){
      it('should match identical casing', function(done){
        var app = express();

        app.enable('case sensitive routing');

        app.get('/uSer', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/uSer')
        .expect('tj', done);
      })

      it('should not match otherwise', function(done){
        var app = express();

        app.enable('case sensitive routing');

        app.get('/uSer', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user')
        .expect(404, done);
      })
    })
  })

  describe('trailing slashes', function(){
    it('should be optional by default', function(done){
      var app = express();

      app.get('/user', function(req, res){
        res.end('tj');
      });

      request(app)
      .get('/user/')
      .expect('tj', done);
    })

    describe('when "strict routing" is enabled', function(){
      it('should match trailing slashes', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user/', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user/')
        .expect('tj', done);
      })

      it('should match no slashes', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user')
        .expect('tj', done);
      })

      it('should fail when omitting the trailing slash', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user/', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user')
        .expect(404, done);
      })

      it('should fail when adding the trailing slash', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user/')
        .expect(404, done);
      })
    })
  })

  it('should allow escaped regexp', function(done){
    var app = express();

    app.get('/user/\\d+', function(req, res){
      res.end('woot');
    });

    request(app)
    .get('/user/10')
    .end(function(res){
      res.statusCode.should.equal(200);
      request(app)
      .get('/user/tj')
      .expect(404, done);
    });
  })

  it('should allow literal "."', function(done){
    var app = express();

    app.get('/api/users/:from..:to', function(req, res){
      var from = req.params.from
        , to = req.params.to;

      res.end('users from ' + from + ' to ' + to);
    });

    request(app)
    .get('/api/users/1..50')
    .expect('users from 1 to 50', done);
  })

  describe('*', function(){
    it('should denote a greedy capture group', function(done){
      var app = express();

      app.get('/user/*.json', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/user/tj.json')
      .expect('tj', done);
    })

    it('should work with several', function(done){
      var app = express();

      app.get('/api/*.*', function(req, res){
        var resource = req.params.shift()
          , format = req.params.shift();
        res.end(resource + ' as ' + format);
      });

      request(app)
      .get('/api/users/0.json')
      .expect('users/0 as json', done);
    })

    it('should allow naming', function(done){
      var app = express();

      app.get('/api/:resource(*)', function(req, res){
        var resource = req.params.resource;
        res.end(resource);
      });

      request(app)
      .get('/api/users/0.json')
      .expect('users/0.json', done);
    })

    it('should span multiple segments', function(done){
      var app = express();

      app.get('/file/*', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/file/javascripts/jquery.js')
      .expect('javascripts/jquery.js', done);
    })

    it('should be optional', function(done){
      var app = express();

      app.get('/file/*', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/file/')
      .expect('', done);
    })

    it('should require a preceeding /', function(done){
      var app = express();

      app.get('/file/*', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/file')
      .expect(404, done);
    })
  })

  describe(':name', function(){
    it('should denote a capture group', function(done){
      var app = express();

      app.get('/user/:user', function(req, res){
        res.end(req.params.user);
      });

      request(app)
      .get('/user/tj')
      .expect('tj', done);
    })

    it('should match a single segment only', function(done){
      var app = express();

      app.get('/user/:user', function(req, res){
        res.end(req.params.user);
      });

      request(app)
      .get('/user/tj/edit')
      .expect(404, done);
    })

    it('should allow several capture groups', function(done){
      var app = express();

      app.get('/user/:user/:op', function(req, res){
        res.end(req.params.op + 'ing ' + req.params.user);
      });

      request(app)
      .get('/user/tj/edit')
      .expect('editing tj', done);
    })
  })

  describe(':name?', function(){
    it('should denote an optional capture group', function(done){
      var app = express();

      app.get('/user/:user/:op?', function(req, res){
        var op = req.params.op || 'view';
        res.end(op + 'ing ' + req.params.user);
      });

      request(app)
      .get('/user/tj')
      .expect('viewing tj', done);
    })

    it('should populate the capture group', function(done){
      var app = express();

      app.get('/user/:user/:op?', function(req, res){
        var op = req.params.op || 'view';
        res.end(op + 'ing ' + req.params.user);
      });

      request(app)
      .get('/user/tj/edit')
      .expect('editing tj', done);
    })
  })

  describe('.:name', function(){
    it('should denote a format', function(done){
      var app = express();

      app.get('/:name.:format', function(req, res){
        res.end(req.params.name + ' as ' + req.params.format);
      });

      request(app)
      .get('/foo.json')
      .expect('foo as json', function(){
        request(app)
        .get('/foo')
        .expect(404, done);
      });
    })
  })

  describe('.:name?', function(){
    it('should denote an optional format', function(done){
      var app = express();

      app.get('/:name.:format?', function(req, res){
        res.end(req.params.name + ' as ' + (req.params.format || 'html'));
      });

      request(app)
      .get('/foo')
      .expect('foo as html', function(){
        request(app)
        .get('/foo.json')
        .expect('foo as json', done);
      });
    })
  })

  describe('when next() is called', function(){
    it('should continue lookup', function(done){
      var app = express()
        , calls = [];

      app.get('/foo/:bar?', function(req, res, next){
        calls.push('/foo/:bar?');
        next();
      });

      app.get('/bar', function(req, res){
        assert(0);
      });

      app.get('/foo', function(req, res, next){
        calls.push('/foo');
        next();
      });

      app.get('/foo', function(req, res, next){
        calls.push('/foo 2');
        res.end('done');
      });

      request(app)
      .get('/foo')
      .expect('done', function(){
        calls.should.eql(['/foo/:bar?', '/foo', '/foo 2']);
        done();
      })
    })
  })

  describe('when next(err) is called', function(){
    it('should break out of app.router', function(done){
      var app = express()
        , calls = [];

      app.get('/foo/:bar?', function(req, res, next){
        calls.push('/foo/:bar?');
        next();
      });

      app.get('/bar', function(req, res){
        assert(0);
      });

      app.get('/foo', function(req, res, next){
        calls.push('/foo');
        next(new Error('fail'));
      });

      app.get('/foo', function(req, res, next){
        assert(0);
      });

      app.use(function(err, req, res, next){
        res.end(err.message);
      })

      request(app)
      .get('/foo')
      .expect('fail', function(){
        calls.should.eql(['/foo/:bar?', '/foo']);
        done();
      })
    })
  })
})
<commit_msg>added param decoding test<commit_after>var express = require('../')
  , request = require('./support/http')
  , assert = require('assert');

describe('app.router', function(){
  describe('methods supported', function(){
    express.methods.forEach(function(method){
      it('should include ' + method.toUpperCase(), function(done){
        var app = express();
        var calls = [];

        app[method]('/foo', function(req, res){
          if ('head' == method) {
            res.end();
          } else {
            res.end(method);
          }
        });

        request(app)
        [method]('/foo')
        .expect('head' == method ? '' : method, done);
      })
    });
  })

  it('should decode params', function(done){
    var app = express();

    app.get('/:name', function(req, res, next){
      res.send(req.params.name);
    });

    request(app)
    .get('/foo%2Fbar')
    .expect('foo/bar', done);
  })

  it('should be .use()able', function(done){
    var app = express();

    var calls = [];

    app.use(function(req, res, next){
      calls.push('before');
      next();
    });

    app.use(app.router);

    app.use(function(req, res, next){
      calls.push('after');
      res.end();
    });

    app.get('/', function(req, res, next){
      calls.push('GET /')
      next();
    });

    request(app)
    .get('/')
    .end(function(res){
      calls.should.eql(['before', 'GET /', 'after'])
      done();
    })
  })

  it('should be auto .use()d on the first app.VERB() call', function(done){
    var app = express();

    var calls = [];

    app.use(function(req, res, next){
      calls.push('before');
      next();
    });

    app.get('/', function(req, res, next){
      calls.push('GET /')
      next();
    });

    app.use(function(req, res, next){
      calls.push('after');
      res.end();
    });

    request(app)
    .get('/')
    .end(function(res){
      calls.should.eql(['before', 'GET /', 'after'])
      done();
    })
  })

  describe('when given a regexp', function(){
    it('should match the pathname only', function(done){
      var app = express();

      app.get(/^\/user\/[0-9]+$/, function(req, res){
        res.end('user');
      });

      request(app)
      .get('/user/12?foo=bar')
      .expect('user', done);
    })

    it('should populate req.params with the captures', function(done){
      var app = express();

      app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function(req, res){
        var id = req.params.shift()
          , op = req.params.shift();
        res.end(op + 'ing user ' + id);
      });

      request(app)
      .get('/user/10/edit')
      .expect('editing user 10', done);
    })
  })

  describe('when given an array', function(){
  	it('should match all paths in the array', function(done){
			var app = express();

			app.get(['/one', '/two'], function(req, res){
				res.end('works');
			});

			request(app)
			.get('/one')
			.expect('works', function() {
				request(app)
				.get('/two')
				.expect('works', done);
			});
		})
	})

  describe('case sensitivity', function(){
    it('should be disabled by default', function(done){
      var app = express();

      app.get('/user', function(req, res){
        res.end('tj');
      });

      request(app)
      .get('/USER')
      .expect('tj', done);
    })

    describe('when "case sensitive routing" is enabled', function(){
      it('should match identical casing', function(done){
        var app = express();

        app.enable('case sensitive routing');

        app.get('/uSer', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/uSer')
        .expect('tj', done);
      })

      it('should not match otherwise', function(done){
        var app = express();

        app.enable('case sensitive routing');

        app.get('/uSer', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user')
        .expect(404, done);
      })
    })
  })

  describe('trailing slashes', function(){
    it('should be optional by default', function(done){
      var app = express();

      app.get('/user', function(req, res){
        res.end('tj');
      });

      request(app)
      .get('/user/')
      .expect('tj', done);
    })

    describe('when "strict routing" is enabled', function(){
      it('should match trailing slashes', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user/', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user/')
        .expect('tj', done);
      })

      it('should match no slashes', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user')
        .expect('tj', done);
      })

      it('should fail when omitting the trailing slash', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user/', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user')
        .expect(404, done);
      })

      it('should fail when adding the trailing slash', function(done){
        var app = express();

        app.enable('strict routing');

        app.get('/user', function(req, res){
          res.end('tj');
        });

        request(app)
        .get('/user/')
        .expect(404, done);
      })
    })
  })

  it('should allow escaped regexp', function(done){
    var app = express();

    app.get('/user/\\d+', function(req, res){
      res.end('woot');
    });

    request(app)
    .get('/user/10')
    .end(function(res){
      res.statusCode.should.equal(200);
      request(app)
      .get('/user/tj')
      .expect(404, done);
    });
  })

  it('should allow literal "."', function(done){
    var app = express();

    app.get('/api/users/:from..:to', function(req, res){
      var from = req.params.from
        , to = req.params.to;

      res.end('users from ' + from + ' to ' + to);
    });

    request(app)
    .get('/api/users/1..50')
    .expect('users from 1 to 50', done);
  })

  describe('*', function(){
    it('should denote a greedy capture group', function(done){
      var app = express();

      app.get('/user/*.json', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/user/tj.json')
      .expect('tj', done);
    })

    it('should work with several', function(done){
      var app = express();

      app.get('/api/*.*', function(req, res){
        var resource = req.params.shift()
          , format = req.params.shift();
        res.end(resource + ' as ' + format);
      });

      request(app)
      .get('/api/users/0.json')
      .expect('users/0 as json', done);
    })

    it('should allow naming', function(done){
      var app = express();

      app.get('/api/:resource(*)', function(req, res){
        var resource = req.params.resource;
        res.end(resource);
      });

      request(app)
      .get('/api/users/0.json')
      .expect('users/0.json', done);
    })

    it('should span multiple segments', function(done){
      var app = express();

      app.get('/file/*', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/file/javascripts/jquery.js')
      .expect('javascripts/jquery.js', done);
    })

    it('should be optional', function(done){
      var app = express();

      app.get('/file/*', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/file/')
      .expect('', done);
    })

    it('should require a preceeding /', function(done){
      var app = express();

      app.get('/file/*', function(req, res){
        res.end(req.params[0]);
      });

      request(app)
      .get('/file')
      .expect(404, done);
    })
  })

  describe(':name', function(){
    it('should denote a capture group', function(done){
      var app = express();

      app.get('/user/:user', function(req, res){
        res.end(req.params.user);
      });

      request(app)
      .get('/user/tj')
      .expect('tj', done);
    })

    it('should match a single segment only', function(done){
      var app = express();

      app.get('/user/:user', function(req, res){
        res.end(req.params.user);
      });

      request(app)
      .get('/user/tj/edit')
      .expect(404, done);
    })

    it('should allow several capture groups', function(done){
      var app = express();

      app.get('/user/:user/:op', function(req, res){
        res.end(req.params.op + 'ing ' + req.params.user);
      });

      request(app)
      .get('/user/tj/edit')
      .expect('editing tj', done);
    })
  })

  describe(':name?', function(){
    it('should denote an optional capture group', function(done){
      var app = express();

      app.get('/user/:user/:op?', function(req, res){
        var op = req.params.op || 'view';
        res.end(op + 'ing ' + req.params.user);
      });

      request(app)
      .get('/user/tj')
      .expect('viewing tj', done);
    })

    it('should populate the capture group', function(done){
      var app = express();

      app.get('/user/:user/:op?', function(req, res){
        var op = req.params.op || 'view';
        res.end(op + 'ing ' + req.params.user);
      });

      request(app)
      .get('/user/tj/edit')
      .expect('editing tj', done);
    })
  })

  describe('.:name', function(){
    it('should denote a format', function(done){
      var app = express();

      app.get('/:name.:format', function(req, res){
        res.end(req.params.name + ' as ' + req.params.format);
      });

      request(app)
      .get('/foo.json')
      .expect('foo as json', function(){
        request(app)
        .get('/foo')
        .expect(404, done);
      });
    })
  })

  describe('.:name?', function(){
    it('should denote an optional format', function(done){
      var app = express();

      app.get('/:name.:format?', function(req, res){
        res.end(req.params.name + ' as ' + (req.params.format || 'html'));
      });

      request(app)
      .get('/foo')
      .expect('foo as html', function(){
        request(app)
        .get('/foo.json')
        .expect('foo as json', done);
      });
    })
  })

  describe('when next() is called', function(){
    it('should continue lookup', function(done){
      var app = express()
        , calls = [];

      app.get('/foo/:bar?', function(req, res, next){
        calls.push('/foo/:bar?');
        next();
      });

      app.get('/bar', function(req, res){
        assert(0);
      });

      app.get('/foo', function(req, res, next){
        calls.push('/foo');
        next();
      });

      app.get('/foo', function(req, res, next){
        calls.push('/foo 2');
        res.end('done');
      });

      request(app)
      .get('/foo')
      .expect('done', function(){
        calls.should.eql(['/foo/:bar?', '/foo', '/foo 2']);
        done();
      })
    })
  })

  describe('when next(err) is called', function(){
    it('should break out of app.router', function(done){
      var app = express()
        , calls = [];

      app.get('/foo/:bar?', function(req, res, next){
        calls.push('/foo/:bar?');
        next();
      });

      app.get('/bar', function(req, res){
        assert(0);
      });

      app.get('/foo', function(req, res, next){
        calls.push('/foo');
        next(new Error('fail'));
      });

      app.get('/foo', function(req, res, next){
        assert(0);
      });

      app.use(function(err, req, res, next){
        res.end(err.message);
      })

      request(app)
      .get('/foo')
      .expect('fail', function(){
        calls.should.eql(['/foo/:bar?', '/foo']);
        done();
      })
    })
  })
})
<|endoftext|>


<commit_before>// NODE ENVIRONMENT

var websocketStream = require('websocket-stream')
var ws = require('ws').Server

var readdir = require('fs').readdir
var http = require('http')
var filed = require('filed')
var through = require('through')
var fern = require('fern')

var map = require('./_map')
var cmp = require('./_cmp')
var level = require('level')

var Bricoleur = require('./_brico')

module.exports = Environment

function Environment (opts, running) {
  if (opts.path_data[(opts.path_data.length-1)] !== '/') opts.path_data += '/'
  if (opts.path_static[(opts.path_static.length-1)] !== '/') opts.path_static += '/'
  if (opts.path_wilds[(opts.path_wilds.length-1)] !== '/') opts.path_wilds += '/'

  var Self = this
  var StaticFiles = {}
  var Data

  var _ = {} // brico scope container // replace with com core --

  // CONFIGURATION

  var CompileOpts = {
    path_wilds:opts.path_wilds,
    path_styl:opts.path_styl,
    path_css:opts.path_css,
    path_bundle:opts.path_bundle,
    path_env:opts.path_js,
    compress:false
  }
  var MapOpts = {
    path_wilds:opts.path_wilds
  }
  var Map = new map(MapOpts, function (s) {// map wilds
    var compiler = new cmp(CompileOpts)
    s.pipe(compiler.s)
    s.on('end', function () {
      console.log('map of '+opts.path_wilds+' complete!')
    })
  })

  // HTTP SERVER FOR STATIC FILES

  readdir(opts.path_static, function GetStaticFiles (e, files) {
    if (e) console.error(e)
    files.forEach(function findStaticFiles (file) {
      StaticFiles[file] = opts.path_static+file
    })
  })

  var Server = http.createServer(HandleRequests)

  function HandleRequests (req, res) {
    var url = req.url
    if (url === '/') url = '/index.html'
    var file = url.replace('/','')
    if (StaticFiles[file]) filed(StaticFiles[file]).pipe(res)
    if (!StaticFiles[file]) res.end() // todo: somekindof 404
  }

  Server.listen(opts.port, function () {
    var uid = parseInt(process.env.SUDO_UID)
    if (uid) process.setuid(uid) // switch to user permissions
    Data = level(opts.path_data+'env') // wait until user permissions are active
    running()
  })

  // WEBSOCKET CONNECTIONS

  var WebSocket = new ws({server:Server})

  WebSocket.on('connection', HandleSoc)

  function HandleSoc (soc) { // fix this thing!
    var wss = websocketStream(soc)
    var headers = soc.upgradeReq.headers
    var key = headers['sec-websocket-key']
    if (headers['sec-websocket-key1']) key = headers['sec-websocket-key1'].replace(/\s/g,'-')
    var host = headers.host

    _[host].addSocket(key, function socketAdded() {
      console.log('opened socket: '+key+' to brico: '+host)
      wss.pipe(_[host][key])
      wss.write(JSON.stringify({'api':['test','xolander']}))
    })
  }

  // API

  var API = {
    load: function (params) {
      var cb = params[0]
      var streamBricos = Data.createValueStream()
      streamBricos.on('data', function (d) {
        var brico = JSON.parse(d)
        console.log(brico)
        _[brico.host] = new Bricoleur()
        _[brico.host].data = level(opts.path_data+brico.host)
      })
      streamBricos.on('end', cb)
    },
    createBrico: function (params) {
      var key = params[0].host
      var val = params[0]
      var cb = params[1]
      Data.put(key, JSON.stringify(val), cb)
    }
  }
  this.api = fern({key:'api',tree:API})
}
<commit_msg>cb with through stream on running<commit_after>// NODE ENVIRONMENT

var websocketStream = require('websocket-stream')
var ws = require('ws').Server

var readdir = require('fs').readdir
var http = require('http')
var filed = require('filed')
var through = require('through')
var fern = require('fern')

var map = require('./_map')
var cmp = require('./_cmp')
var level = require('level')

var Bricoleur = require('./_brico')

inherits(Environment, Stream)

module.exports = Environment

function Environment (opts, running) {
  if (opts.path_data[(opts.path_data.length-1)] !== '/') opts.path_data += '/'
  if (opts.path_static[(opts.path_static.length-1)] !== '/') opts.path_static += '/'
  if (opts.path_wilds[(opts.path_wilds.length-1)] !== '/') opts.path_wilds += '/'

  var StaticFiles = {}
  var Data

  var _ = {} // brico scope container // replace with com core --

  this.s = through(function write (chunk) {
    this.queue(chunk)
  }, function end () {
    this.emit('end')
  })

  // CONFIGURATION

  var CompileOpts = {
    path_wilds:opts.path_wilds,
    path_styl:opts.path_styl,
    path_css:opts.path_css,
    path_bundle:opts.path_bundle,
    path_env:opts.path_js,
    compress:false
  }
  var MapOpts = {
    path_wilds:opts.path_wilds
  }
  var Map = new map(MapOpts, function (s) {// map wilds
    var compiler = new cmp(CompileOpts)
    s.pipe(compiler.s)
    s.on('end', function () {
      console.log('map of '+opts.path_wilds+' complete!')
    })
  })

  // HTTP SERVER FOR STATIC FILES

  readdir(opts.path_static, function GetStaticFiles (e, files) {
    if (e) console.error(e)
    files.forEach(function findStaticFiles (file) {
      StaticFiles[file] = opts.path_static+file
    })
  })

  var Server = http.createServer(HandleRequests)

  function HandleRequests (req, res) {
    var url = req.url
    if (url === '/') url = '/index.html'
    var file = url.replace('/','')
    if (StaticFiles[file]) filed(StaticFiles[file]).pipe(res)
    if (!StaticFiles[file]) res.end() // todo: somekindof 404
  }

  Server.listen(opts.port, function () {
    var uid = parseInt(process.env.SUDO_UID)
    if (uid) process.setuid(uid) // switch to user permissions
    Data = level(opts.path_data+'env') // wait until user permissions are active
    running(Self.s)
  })

  // WEBSOCKET CONNECTIONS

  var WebSocket = new ws({server:Server})

  WebSocket.on('connection', HandleSoc)

  function HandleSoc (soc) { // fix this thing!
    var wss = websocketStream(soc)
    var headers = soc.upgradeReq.headers
    var key = headers['sec-websocket-key']
    if (headers['sec-websocket-key1']) key = headers['sec-websocket-key1'].replace(/\s/g,'-')
    var host = headers.host

    _[host].addSocket(key, function socketAdded() {
      console.log('opened socket: '+key+' to brico: '+host)
      wss.pipe(_[host][key])
      wss.write(JSON.stringify({'api':['test','xolander']}))
    })
  }

  // API

  var API = {
    load: function (u, cb) {
      var streamBricos = Data.createValueStream()
      streamBricos.on('data', function (d) {
        var brico = JSON.parse(d)
        _[brico.host] = new Bricoleur()
        _[brico.host].data = level(opts.path_data+brico.host)
      })
      streamBricos.on('end', function () {
        cb('done!')
      })
    },
    createBrico: function (brico, cb) {
      Data.put(brico.key, JSON.stringify(brico), cb)
    }
  }
  this.api = new fern({key:'api',tree:API})
  Self.s.pipe(Self.api)
}
<|endoftext|>


<commit_before><commit_msg>Implement basic tests for users route<commit_after>// POST /users/ Creates a new user.
// Write a test that validates that a new user created is unique.
// Write a test that validates that a new user created has a role defined. **
// Write a test that validates that a new user created both first and last names.
// Write a test that validates that all users are returned.
// GET /users/ Find matching instances of user.
// GET /users/<id> Find user.
// PUT /users/<id> Update user attributes.
// DELETE /users/<id> Delete user.

'use strict';

const expect    = require('chai').expect;
const supertest = require('supertest');
const server    = require('./../../index');
const api       = supertest(server);

const userData  = require('./data/user-data.js');

describe('User', () => {
  it('should create a new user', (done) => {
    api
      .post('/api/users/')
      .send(userData.normalUser1)
      .expect(201)
      .end((err, res) => {
        expect(res.message).to.equal('User created');
        expect(res.data).to.have.property('id');
        done(err);
      });
  });

  it('should create unique users', (done) => {
    // duplicate email address
    api
      .post('/api/users/')
      .send(userData.duplicateUser2)
      .expect(409)
      .end((err, res) => {
        expect(res.message).to.equal('Duplicate email address');
        done(err);
      });

    //duplicate username test
    api
      .post('/api/users/')
      .send(userData.duplicateUser1)
      .expect(409)
      .end((err, res) => {
        expect(res.message).to.equal('Duplicate username');
        done(err);
      });
  });

  it('should create firstname and lastname for a new user', (done) => {
    api
      .post('/api/users/')
      .send(userData.normalUser2)
      .expect(201)
      .end((err, res) => {
        expect(res.message).to.equal('User created');
        expect(res.data).to.have.property('firstname');
        expect(res.data.firstname).to.equal('Winner');
        expect(res.data).to.have.property('lastname');
        expect(res.data.firstname).to.equal('Bolorunduro');
        done(err);
      });
  });

  it('should return all the users in the database', (done) => {
    api
      .get('/api/users/')
      .expect(200)
      .end((err, res) => {
        expect(res.data.length).to.equal(3);
        done(err);
      });
  });

  it('should find a user', (done) => {
    api
    .get('/api/users/1')
    .expect(200)
    .end((err, res) => {
      done(err);
    });
  });

  it('should update user attribute', (done) => {
    api
      .put('/api/users/1')
      .send({
        lastname: 'Adetola'
      })
      .expect(204)
      .end((err, res) => {
        done(err);
      });
  });

  it('should delete an exisiting user', (done) => {
    api
      .delete('/api/users/3')
      .expect(200)
      .end((err, res) => {
        done(err);
      });
  })
});
<|endoftext|>