<commit_before>var Validator = require('validator').Validator;
var validators = require('validator').validators;
var check = require('validator').check;

/*VeryType is a wrapper for node-validator, that allows you to re-use
 * validation chains.
 * See https://github.com/chriso/node-validator
 */
function VeryType() {
    this.validations = [];
    this.v = new Validator;
    this.errors = [];

    //override Validator errors so exceptions aren't raised
    //and so that we can gather error messages
    this.v.error = function(msg) {
        this.errors.push(msg);
    }.bind(this);
}

(function() {

    //iterate through all of node-validator's validators
    //to create wrapper functions on VeryType
    var valfuncs = Object.keys(validators);
    valfuncs.forEach(function(vfunc) {
        this[vfunc] = function () {
            var args = Array.prototype.splice.call(arguments, 0);
            //each wrapper records the function and args
            //for later chaining
            this.validations.push({vfunc: vfunc, args: args});
            return this;
        };
    }.bind(this));

    delete valfuncs;

    //run the recorded validator chain and return the errors
    this.validate = function(value) {
        this.errors = [];
        var c = this.v.check(value);
        for (var vidx in this.validations) {
            var conf = this.validations[vidx];
            if (conf.vfunc) {
                c = this.v[conf.vfunc].apply(this.v, conf.args);
            }
        }
        return this.errors;
    };

    this.isType = function (value, type) {
        return (typeof value === type);
    };

    this.isArray = function (value) {
        return (Array.isArray(value));
    };

    this.custom = function(value, func) {
        return func(value);
    };

}).call(VeryType.prototype);

var Types = {
    'number': new VeryType().isNumeric(),
    'alpha': new VeryType().isAlpha(),
    'alphanumeric': new VeryType().isAlphanumeric(),
    'date': new VeryType().isDate(),
    'email': new VeryType().isEmail(),
    'boolean': new VeryType().isType('boolean'),
    'function': new VeryType().isType('function'),
};

/* VeryCollection: array-like object
 * for managaging lists/collections of models
 * of a single model type
 */
function VeryCollection(modeldef, parent) {
    this.modeldef = modeldef;
    this.parent = parent;

    //we treat local number properties as the items
    this.__defineGetter__('length', function () {
        var keys = Object.keys(this);
        var count = 0;
        keys.forEach(function(key) {
            if(typeof key === 'number' || key.match(/^[0-9]+$/)) {
                count++;
            }
        });
        return count;
    });
}

(function() {

    this.pop = function () {
    };

    this.push = function (value) {
        var model = this.modeldef.create(value);
        model.__parent = this;
        this[Number(this.length)] = model;
        return model;
    };

    this.delete = function(idx) {
        delete this[idx];
    };

    this.forEach = function(cb) {
        var keys = Object.keys(this).sort();
        keys.forEach(function(key) {
            if (typeof key === 'number' || key.match(/^[0-9]+$/)) {
                cb(this[key]);
            }
        }.bind(this));
    };

}).call(VeryCollection.prototype);

/* VeryModel, for spawning models out of a definition
 * Instances of VeryModel are factories for models themselves.
 */
function VeryModel(definition, args) {
    args = args || {};
    this.definition = definition;
    this.savecb = args.saveFunction;
    this.loadcb = args.loadFunction;

    //initialize sub model definitions recursively
    this.fields = Object.keys(definition);
    this.fields.forEach(function (field) {
        if (this.definition[field].hasOwnProperty('model')) {
            //if we get a VeryModel instance instead of a definition object
            //we have to switch things up
            if (this.definition[field].model instanceof VeryModel) {
                this.definition[field].subModel = this.definition[field].model;
                this.definition[field].model = this.definition[field].subModel.definition;
            } else {
                //initialize sub VeryModel
                this.definition[field].subModel = new VeryModel(this.definition[field].model);
            }
        } else if (this.definition[field].hasOwnProperty('collection')) {
            //if we get a VeryModel instance instead of a definition object
            //we have to switch things up
            if (this.definition[field].collection instanceof VeryModel) {
                this.definition[field].subVeryCollection = this.definition[field].collection;
                this.definition[field].collection = this.definition[field].subVeryCollection.definition;
            } else {
                //initialize sub model defintion
                this.definition[field].subVeryCollection = new VeryModel(this.definition[field].collection);
            }
        }
    }.bind(this));
}

(function() {

    /* create (optionally with the value object)
     * generates a model for populating, reading, and validating
     * the object over time
     */
    this.create = function (value) {
        var model = new Object;
        model.__verymodel = this;
        model.__defs = this.definition;
        model.__data = {};
        if (Array.isArray(model.__defs)) {
            model.__data = [];
        }
        model.__map = {};
        model.__reverse_map = {};
        model.__primary_key = null;
        model.__creating = true;
        //run through the definition fields
        this.fields.forEach(function(field) {
            //hidden value attribute accessor
            if (typeof model.__defs[field].keyword !== 'undefined') {
                model.__map[model.__defs[field].keyword] = field;
                model.__reverse_map[field] = model.__defs[field].keyword;
                model.__defineGetter__(model.__defs[field].keyword, function() {
                    return model[model.__map[model.__defs[field].keyword]];
                });
            }
            if (model.__defs[field].primary === true) {
                model.__primary_key = field;
            }
            model.__defineGetter__(field, function() {
                if (this.__defs[field].hasOwnProperty('derive')) {
                    this.__data[field] = this.__defs[field].derive(this);
                }
                return this.__data[field];
            }.bind(model));
            //handle assignment to sub models, sub arraymodels, and values differently
            if (model.__defs[field].hasOwnProperty('model')) {
                //recursively instatiate submodel
                model.__data[field] = model.__defs[field].subModel.create();
                model.__data[field].__parent = model;
                //assigning to the submodel should call loadData on it
                model.__defineSetter__(field, function(value) {
                    this.__data[field].loadData(value);
                });
            } else if (model.__defs[field].hasOwnProperty('collection')) {
                //create an array-like object for a collection of the sub model
                model.__data[field] = new VeryCollection(model.__defs[field].subVeryCollection, model);
            } else {
                //assign the default value to required fields
                //this could get overwritten later at the loadData phase
                if (model.__defs[field].required) {
                    model.__data[field] = model.__defs[field].default;
                }
                //validate values as they come in
                //hidden value style setter
                model.__defineSetter__(field, function(value) {
                    if ((this.__defs.hasOwnProperty('type') && this.__defs.type.validate(value)) || !this.__defs.hasOwnProperty('type')) {
                        if (this.__defs[field].static === true && !this.__creating) {
                            throw new Error('Cannot set static values');
                        }
                        this.__data[field] = value;
                    }
                })
            }
        }.bind(this));

        //load data en masse into the model
        //works recursively
        model.loadData = function (value) {
            if (Array.isArray(this.__defs)) {
                this.__data = [];
                var valueoff = 0;
                for (var vidx in this.__defs) {
                    if (!this.__defs[vidx].required && value.length + valueoff < this.__defs.length) {
                        this[vidx] = this.__defs[vidx].default;
                        valueoff += 1;
                    } else {
                        this[vidx] = value[vidx - valueoff];
                    }
                }
            } else {
                Object.keys(value).forEach(function (key) {
                    if (this.__defs[key].hasOwnProperty('collection')) {
                        for (var vidx in value[key]) {
                            this.__data[key].push(value[key][vidx]);
                        }
                    } else {
                        model[key] = value[key];
                    }
                }.bind(this));
            }
        };

        //create a raw Javascript object out of the model data
        //no more helper functions or accessors
        model.toObject = function (opts) {
            opts = opts || {};
            if (Array.isArray(this.__defs) && !opts.useKeywords) {
                var obj = new Array();
            } else {
                var obj = new Object();
            }
            Object.keys(this.__defs).forEach(function(field) {
                var key = field;
                if (this.__defs[field].private && !opts.withPrivate) {
                    return;
                }
                if (opts.useKeywords && this.__reverse_map.hasOwnProperty(field)) {
                    key = this.__reverse_map[field];
                }
                if (this.__defs[field].hasOwnProperty('model')) {
                    obj[key] = this.__data[field].toObject();
                } else if (this.__defs[field].hasOwnProperty('collection')) {
                    obj[key] = [];
                    this.__data[field].forEach(function (inst) {
                        obj[key].push(inst.toObject());
                    });
                } else {
                    obj[key] = this[field];
                    if (typeof obj[key] === 'undefined') {
                        delete obj[key];
                    }
                }

            }.bind(this));
            return obj;
        };

        //validate that a model follows the rules and types
        //works recursively
        //returns a list of errors
        //if the list of errors.length === 0, you're golden
        model.doValidate = function () {
            var errors = [];
            Object.keys(this.__defs).forEach(function(field) {
                var fidx;
                var merrors;
                //if field is set and dependency is set
                if (this.__data.hasOwnProperty(field) && this.__defs[field].depends) {
                    for (fidx in this.__defs[field].depends) {
                        //check to see if other field is set
                        if (!this.__data.hasOwnProperty(fidx) || this.__data[fidx] === undefined) {
                            errors.push(field + ": Dependency -> " + fidx + ": not set");
                        //check to see if dependency has a validator
                        } else if (this.__defs[field].depends instanceof VeryType) {
                            merrors = this.__defs[field].depends[fidx].validate(this.__data[fidx]);
                            merrors.forEach(function (error) {
                                errors.push(field + ": Dependency  -> " + fidx + ": " + error);
                            });
                        }
                    }
                }
                //if field is reuqired and it's not set
                if (this.__defs[field].required && (!this.__data.hasOwnProperty(field) || this.__data[field] === undefined)) {
                    errors.push(field + ": required");
                }
                //if this field is another model
                //recursively validate it
                //add errors to our own
                if (this.__defs[field].hasOwnProperty('model') && this.__data.hasOwnProperty(field)) {
                    merrors = this.__data[field].doValidate();
                    for (var eidx in merrors) {
                        merrors[eidx] = field + '.' + merrors[eidx];
                    }
                    errors = errors.concat(merrors);
                //if field is an array of models
                //recursively validate them
                //and add their errors to our own
                } else if (this.__defs[field].hasOwnProperty('collection') && this.__data.hasOwnProperty(field)) {
                    var arrayerrors = [];
                    var idx = 0;
                    this.__data[field].forEach( function (model) {
                        merrors = model.doValidate();
                        for (var eidx in merrors) {
                            merrors[eidx] = field + '[' + idx + '].' + merrors[eidx];
                        }
                        errors = errors.concat(merrors);
                        idx += 1;
                    });
                //if we have a VeryType to validate against, validate it
                } else if (this.__data.hasOwnProperty(field) && this.__defs[field].hasOwnProperty('type')) {
                    if (typeof this.__defs[field].type === 'string' && Types.hasOwnProperty(this.__defs[field].type)) {
                        this.__defs[field].type = Types[this.__defs[field].type];
                    }
                    merrors = this.__defs[field].type.validate(this.__data[field]);
                    merrors.forEach(function (error) {
                        errors.push(field + ": " + error);
                    });
                } else if (this.__data.hasOwnProperty(field) && this.__defs[field].hasOwnProperty('array')) {
                    var tidx = 0;
                    this.__defs[field].array.forEach(function (type) {
                        merrors = type.validate(this.__data[field][tidx]);
                        merrors.forEach(function (error) {
                            errors.push(field + "[" + tidx + "]: " + error);
                        });
                        tidx++;
                    }.bind(this));
                }

            }.bind(this));
            return errors;
        };

        model.makeClone = function () {
            return this.__verymodel.create(this.toObject());
        };

        //generate a JSON string from the model data
        model.toJSON = function (args) {
            return JSON.stringify(this.toObject(args));
        };

        model.doSave = function (cb) {
            this.__verymodel.savecb(this, cb);
        };

        //if model.create passed in initial values, load them
        if (typeof value !== 'undefined') {
            model.loadData(value);
        }

        model.__creating = false;

        return model;
    };

    this.setSave = function (savecb) {
        this.savecb = savecb;
    };

    this.setLoad = function (loaddb) {
        this.loaddb = loaddb;
    };

    this.load = function (id, cb) {
        this.loaddb(id, cb);
    };

}).call(VeryModel.prototype);

module.exports = {
    VeryModel: VeryModel,
    VeryType: function () { return new VeryType; },
    VeryCollection: VeryCollection,
};

<commit_msg>add static fields<commit_after>var Validator = require('validator').Validator;
var validators = require('validator').validators;
var check = require('validator').check;

/*VeryType is a wrapper for node-validator, that allows you to re-use
 * validation chains.
 * See https://github.com/chriso/node-validator
 */
function VeryType() {
    this.validations = [];
    this.v = new Validator;
    this.errors = [];

    //override Validator errors so exceptions aren't raised
    //and so that we can gather error messages
    this.v.error = function(msg) {
        this.errors.push(msg);
    }.bind(this);
}

(function() {

    //iterate through all of node-validator's validators
    //to create wrapper functions on VeryType
    var valfuncs = Object.keys(validators);
    valfuncs.forEach(function(vfunc) {
        this[vfunc] = function () {
            var args = Array.prototype.splice.call(arguments, 0);
            //each wrapper records the function and args
            //for later chaining
            this.validations.push({vfunc: vfunc, args: args});
            return this;
        };
    }.bind(this));

    delete valfuncs;

    //run the recorded validator chain and return the errors
    this.validate = function(value) {
        this.errors = [];
        var c = this.v.check(value);
        for (var vidx in this.validations) {
            var conf = this.validations[vidx];
            if (conf.vfunc) {
                c = this.v[conf.vfunc].apply(this.v, conf.args);
            }
        }
        return this.errors;
    };

    this.isType = function (value, type) {
        return (typeof value === type);
    };

    this.isArray = function (value) {
        return (Array.isArray(value));
    };

    this.custom = function(value, func) {
        return func(value);
    };

}).call(VeryType.prototype);

var Types = {
    'number': new VeryType().isNumeric(),
    'alpha': new VeryType().isAlpha(),
    'alphanumeric': new VeryType().isAlphanumeric(),
    'date': new VeryType().isDate(),
    'email': new VeryType().isEmail(),
    'boolean': new VeryType().isType('boolean'),
    'function': new VeryType().isType('function'),
};

/* VeryCollection: array-like object
 * for managaging lists/collections of models
 * of a single model type
 */
function VeryCollection(modeldef, parent) {
    this.modeldef = modeldef;
    this.parent = parent;

    //we treat local number properties as the items
    this.__defineGetter__('length', function () {
        var keys = Object.keys(this);
        var count = 0;
        keys.forEach(function(key) {
            if(typeof key === 'number' || key.match(/^[0-9]+$/)) {
                count++;
            }
        });
        return count;
    });
}

(function() {

    this.pop = function () {
    };

    this.push = function (value) {
        var model = this.modeldef.create(value);
        model.__parent = this;
        this[Number(this.length)] = model;
        return model;
    };

    this.delete = function(idx) {
        delete this[idx];
    };

    this.forEach = function(cb) {
        var keys = Object.keys(this).sort();
        keys.forEach(function(key) {
            if (typeof key === 'number' || key.match(/^[0-9]+$/)) {
                cb(this[key]);
            }
        }.bind(this));
    };

}).call(VeryCollection.prototype);

/* VeryModel, for spawning models out of a definition
 * Instances of VeryModel are factories for models themselves.
 */
function VeryModel(definition, args) {
    args = args || {};
    this.definition = definition;
    this.savecb = args.saveFunction;
    this.loadcb = args.loadFunction;

    //initialize sub model definitions recursively
    this.fields = Object.keys(definition);
    this.fields.forEach(function (field) {
        if (this.definition[field].hasOwnProperty('model')) {
            //if we get a VeryModel instance instead of a definition object
            //we have to switch things up
            if (this.definition[field].model instanceof VeryModel) {
                this.definition[field].subModel = this.definition[field].model;
                this.definition[field].model = this.definition[field].subModel.definition;
            } else {
                //initialize sub VeryModel
                this.definition[field].subModel = new VeryModel(this.definition[field].model);
            }
        } else if (this.definition[field].hasOwnProperty('collection')) {
            //if we get a VeryModel instance instead of a definition object
            //we have to switch things up
            if (this.definition[field].collection instanceof VeryModel) {
                this.definition[field].subVeryCollection = this.definition[field].collection;
                this.definition[field].collection = this.definition[field].subVeryCollection.definition;
            } else {
                //initialize sub model defintion
                this.definition[field].subVeryCollection = new VeryModel(this.definition[field].collection);
            }
        }
    }.bind(this));
}

(function() {

    /* create (optionally with the value object)
     * generates a model for populating, reading, and validating
     * the object over time
     */
    this.create = function (value) {
        var model = new Object;
        model.__verymodel = this;
        model.__defs = this.definition;
        model.__data = {};
        if (Array.isArray(model.__defs)) {
            model.__data = [];
        }
        model.__map = {};
        model.__reverse_map = {};
        model.__primary_key = null;
        model.__creating = true;
        //run through the definition fields
        this.fields.forEach(function(field) {
            //hidden value attribute accessor
            if (typeof model.__defs[field].keyword !== 'undefined') {
                model.__map[model.__defs[field].keyword] = field;
                model.__reverse_map[field] = model.__defs[field].keyword;
                model.__defineGetter__(model.__defs[field].keyword, function() {
                    return model[model.__map[model.__defs[field].keyword]];
                });
            }
            if (model.__defs[field].primary === true) {
                model.__primary_key = field;
            }
            model.__defineGetter__(field, function() {
                if (this.__defs[field].hasOwnProperty('derive')) {
                    this.__data[field] = this.__defs[field].derive(this);
                }
                return this.__data[field];
            }.bind(model));
            //handle assignment to sub models, sub arraymodels, and values differently
            if (model.__defs[field].hasOwnProperty('model')) {
                //recursively instatiate submodel
                model.__data[field] = model.__defs[field].subModel.create();
                model.__data[field].__parent = model;
                //assigning to the submodel should call loadData on it
                model.__defineSetter__(field, function(value) {
                    this.__data[field].loadData(value);
                });
            } else if (model.__defs[field].hasOwnProperty('collection')) {
                //create an array-like object for a collection of the sub model
                model.__data[field] = new VeryCollection(model.__defs[field].subVeryCollection, model);
            } else {
                //assign the default value to required fields
                //this could get overwritten later at the loadData phase
                if (model.__defs[field].required) {
                    if (typeof model.__defs[field].default === 'function') {
                        mode.__data[field] = model.__defs[field].default(model);
                    } else {
                        model.__data[field] = model.__defs[field].default;
                    }
                }
                //validate values as they come in
                //hidden value style setter
                model.__defineSetter__(field, function(value) {
                    if ((this.__defs.hasOwnProperty('type') && this.__defs.type.validate(value)) || !this.__defs.hasOwnProperty('type')) {
                        if (this.__defs[field].static === true && !this.__creating) {
                            throw new Error('Cannot set static values');
                        }
                        this.__data[field] = value;
                    }
                })
            }
        }.bind(this));

        //load data en masse into the model
        //works recursively
        model.loadData = function (value) {
            if (Array.isArray(this.__defs)) {
                this.__data = [];
                var valueoff = 0;
                for (var vidx in this.__defs) {
                    if (!this.__defs[vidx].required && value.length + valueoff < this.__defs.length) {
                        if (this.__defs[vidx].default === 'function') {
                            this[vidx] = this.__defs[vidx].default(this);
                        } else {
                            this[vidx] = this.__defs[vidx].default;
                        }
                        valueoff += 1;
                    } else {
                        this[vidx] = value[vidx - valueoff];
                    }
                }
            } else {
                Object.keys(value).forEach(function (key) {
                    if (this.__defs[key].hasOwnProperty('collection')) {
                        for (var vidx in value[key]) {
                            this.__data[key].push(value[key][vidx]);
                        }
                    } else {
                        model[key] = value[key];
                    }
                }.bind(this));
            }
        };

        //create a raw Javascript object out of the model data
        //no more helper functions or accessors
        model.toObject = function (opts) {
            opts = opts || {};
            if (Array.isArray(this.__defs) && !opts.useKeywords) {
                var obj = new Array();
            } else {
                var obj = new Object();
            }
            Object.keys(this.__defs).forEach(function(field) {
                var key = field;
                if (this.__defs[field].private && !opts.withPrivate) {
                    return;
                }
                if (opts.useKeywords && this.__reverse_map.hasOwnProperty(field)) {
                    key = this.__reverse_map[field];
                }
                if (this.__defs[field].hasOwnProperty('model')) {
                    obj[key] = this.__data[field].toObject();
                } else if (this.__defs[field].hasOwnProperty('collection')) {
                    obj[key] = [];
                    this.__data[field].forEach(function (inst) {
                        obj[key].push(inst.toObject());
                    });
                } else {
                    obj[key] = this[field];
                    if (typeof obj[key] === 'undefined') {
                        delete obj[key];
                    }
                }

            }.bind(this));
            return obj;
        };

        //validate that a model follows the rules and types
        //works recursively
        //returns a list of errors
        //if the list of errors.length === 0, you're golden
        model.doValidate = function () {
            var errors = [];
            Object.keys(this.__defs).forEach(function(field) {
                var fidx;
                var merrors;
                //if field is set and dependency is set
                if (this.__data.hasOwnProperty(field) && this.__defs[field].depends) {
                    for (fidx in this.__defs[field].depends) {
                        //check to see if other field is set
                        if (!this.__data.hasOwnProperty(fidx) || this.__data[fidx] === undefined) {
                            errors.push(field + ": Dependency -> " + fidx + ": not set");
                        //check to see if dependency has a validator
                        } else if (this.__defs[field].depends instanceof VeryType) {
                            merrors = this.__defs[field].depends[fidx].validate(this.__data[fidx]);
                            merrors.forEach(function (error) {
                                errors.push(field + ": Dependency  -> " + fidx + ": " + error);
                            });
                        }
                    }
                }
                //if field is reuqired and it's not set
                if (this.__defs[field].required && (!this.__data.hasOwnProperty(field) || this.__data[field] === undefined)) {
                    errors.push(field + ": required");
                }
                //if this field is another model
                //recursively validate it
                //add errors to our own
                if (this.__defs[field].hasOwnProperty('model') && this.__data.hasOwnProperty(field)) {
                    merrors = this.__data[field].doValidate();
                    for (var eidx in merrors) {
                        merrors[eidx] = field + '.' + merrors[eidx];
                    }
                    errors = errors.concat(merrors);
                //if field is an array of models
                //recursively validate them
                //and add their errors to our own
                } else if (this.__defs[field].hasOwnProperty('collection') && this.__data.hasOwnProperty(field)) {
                    var arrayerrors = [];
                    var idx = 0;
                    this.__data[field].forEach( function (model) {
                        merrors = model.doValidate();
                        for (var eidx in merrors) {
                            merrors[eidx] = field + '[' + idx + '].' + merrors[eidx];
                        }
                        errors = errors.concat(merrors);
                        idx += 1;
                    });
                //if we have a VeryType to validate against, validate it
                } else if (this.__data.hasOwnProperty(field) && this.__defs[field].hasOwnProperty('type')) {
                    if (typeof this.__defs[field].type === 'string' && Types.hasOwnProperty(this.__defs[field].type)) {
                        this.__defs[field].type = Types[this.__defs[field].type];
                    }
                    merrors = this.__defs[field].type.validate(this.__data[field]);
                    merrors.forEach(function (error) {
                        errors.push(field + ": " + error);
                    });
                } else if (this.__data.hasOwnProperty(field) && this.__defs[field].hasOwnProperty('array')) {
                    var tidx = 0;
                    this.__defs[field].array.forEach(function (type) {
                        merrors = type.validate(this.__data[field][tidx]);
                        merrors.forEach(function (error) {
                            errors.push(field + "[" + tidx + "]: " + error);
                        });
                        tidx++;
                    }.bind(this));
                }

            }.bind(this));
            return errors;
        };

        model.makeClone = function () {
            return this.__verymodel.create(this.toObject());
        };

        //generate a JSON string from the model data
        model.toJSON = function (args) {
            return JSON.stringify(this.toObject(args));
        };

        model.doSave = function (cb) {
            this.__verymodel.savecb(this, cb);
        };

        //if model.create passed in initial values, load them
        if (typeof value !== 'undefined') {
            model.loadData(value);
        }

        model.__creating = false;

        return model;
    };

    this.setSave = function (savecb) {
        this.savecb = savecb;
    };

    this.setLoad = function (loaddb) {
        this.loaddb = loaddb;
    };

    this.load = function (id, cb) {
        this.loaddb(id, cb);
    };

}).call(VeryModel.prototype);

module.exports = {
    VeryModel: VeryModel,
    VeryType: function () { return new VeryType; },
    VeryCollection: VeryCollection,
};

<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var fs = require('fs');
var jspm = require('jspm')
var jspm_config = require('jspm/lib/config');
jspm_config.loadSync();

var sass;
try {
    sass = require('node-sass');
} catch(e) {
    sass = require('gulp-sass').compiler;
}

module.exports.importer = function(url, prev, done) {
    if(url.substr(0, 5) != 'jspm:')
        return done(); // bailout

    url = url.replace(/^jspm:/, '')+'.scss';

    jspm.normalize(url).then(function(path) {
        path = path.replace(/file:\/\/(.*?)(\.js)?$/, '$1');
        try {
            var stat = fs.statSync(path);
        } catch(e) {
            return done();
        }
        if(stat.isFile()) {
            done({
                file: path
            });
        } else {
            done();
        }
    }, function(e) {
        console.log("Could not resolve path:", url)
        done();
    });
};

module.exports.resolve_function = function(path_prefix) {
    path_prefix = path_prefix || '';
    return {
        'jspm_resolve($exp)': function(exp, done) {
            jspm.normalize(exp.getValue()).then(function(respath) {
                respath = respath.replace(/file:\/\/(.*?)(\.js)?$/, '$1');
                var res = path.join(path_prefix, path.relative(jspm_config.pjson.packages, respath))
                done(new sass.types.String(res));
            }, function(e) {
                done(sass.compiler.types.Null());
            });
        }
    };
};
<commit_msg>feature(importer): support underscore in @import file name for partials<commit_after>'use strict';

var path = require('path');
var fs = require('fs');
var jspm = require('jspm')
var jspm_config = require('jspm/lib/config');
jspm_config.loadSync();

var sass;
try {
    sass = require('node-sass');
} catch(e) {
    sass = require('gulp-sass').compiler;
}

module.exports.importer = function(url, prev, done) {
    if(url.substr(0, 5) != 'jspm:')
        return done(); // bailout

    url = url.replace(/^jspm:/, '')+'.scss';

    jspm.normalize(url).then(function(path) {
        var stat;
        var parts;

        path = path.replace(/file:\/\/(.*?)(\.js)?$/, '$1');
        try {
          stat = fs.statSync(path);
        } catch (e) {
          try {
            parts = path.split('/');
            parts[parts.length - 1] = '_' + parts[parts.length - 1];
            path = parts.join('/');
            stat = fs.statSync(path);
          } catch (e) {
            return done();
          }
        }
        if(stat.isFile()) {
            done({
                file: path
            });
        } else {
            done();
        }
    }, function(e) {
        console.log("Could not resolve path:", url)
        done();
    });
};

module.exports.resolve_function = function(path_prefix) {
    path_prefix = path_prefix || '';
    return {
        'jspm_resolve($exp)': function(exp, done) {
            jspm.normalize(exp.getValue()).then(function(respath) {
                respath = respath.replace(/file:\/\/(.*?)(\.js)?$/, '$1');
                var res = path.join(path_prefix, path.relative(jspm_config.pjson.packages, respath))
                done(new sass.types.String(res));
            }, function(e) {
                done(sass.compiler.types.Null());
            });
        }
    };
};
<|endoftext|>


<commit_before>var net = require('net');
var fifo = require('fifo');
var once = require('once');
var speedometer = require('speedometer');
var peerWireProtocol = require('peer-wire-protocol');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var HANDSHAKE_TIMEOUT = 25000;
var RECONNECT_WAIT = [1000, 5000, 15000, 30000, 60000, 120000, 300000, 600000];
var DEFAULT_SIZE = 100;

var toBuffer = function(str, encoding) {
	return Buffer.isBuffer(str) ? str : new Buffer(str, encoding);
};

var toAddress = function(wire) {
	if (typeof wire === 'string') return wire;
	return wire.peerAddress;
};

var onwire = function(connection, onhandshake) {
	var wire = peerWireProtocol();

	connection.on('end', function() {
		connection.destroy();
	});
	connection.on('error', function() {
		connection.destroy();
	});
	connection.on('close', function() {
		wire.destroy();
	});

	var destroy = function() {
		connection.destroy();
	};
	var timeout = setTimeout(destroy, HANDSHAKE_TIMEOUT);

	wire.once('handshake', function(infoHash, peerId) {
		clearTimeout(timeout);
		onhandshake(infoHash, peerId);
	});

	connection.pipe(wire).pipe(connection);
	return wire;
};

var pools = {};

var leave = function(port, swarm) {
	if (!pools[port]) return;
	delete pools[port].swarms[swarm.infoHash.toString('hex')];

	if (Object.keys(pools[port].swarms).length) return;
	pools[port].server.close();
	delete pools[port];
};

var join = function(port, swarm) {
	var pool = pools[port];

	if (!pool) {
		var swarms = {};
		var server = net.createServer(function(connection) {
			var wire = onwire(connection, function(infoHash, peerId) {
				var swarm = swarms[infoHash.toString('hex')];
				if (!swarm) return connection.destroy();
				swarm._onincoming(connection, wire);
			});
		});

		server.listen(port, function() {
			pool.listening = true;
			Object.keys(swarms).forEach(function(infoHash) {
				swarms[infoHash].emit('listening');
			});
		});

		pool = pools[port] = {
			server: server,
			swarms: swarms,
			listening: false
		};
	}

	var infoHash = swarm.infoHash.toString('hex');

	if (pool.listening) {
		process.nextTick(function() {
			swarm.emit('listening');
		});
	}
	if (pool.swarms[infoHash]) {
		process.nextTick(function() {
			swarm.emit('error', new Error('port and info hash already in use'));
		});
		return;
	}

	pool.swarms[infoHash] = swarm;
};

var Swarm = function(infoHash, peerId, options) {
	if (!(this instanceof Swarm)) return new Swarm(infoHash, peerId, options);
	EventEmitter.call(this);

	options = options || {};
	this.handshake = options.handshake;

	this.port = 0;
	this.size = options.size || DEFAULT_SIZE;

	this.infoHash = toBuffer(infoHash, 'hex');
	this.peerId = toBuffer(peerId, 'utf-8');

	this.downloaded = 0;
	this.uploaded = 0;
	this.connections = [];
	this.wires = [];
	this.paused = false;

	this.uploaded = 0;
	this.downloaded = 0;

	this.downloadSpeed = speedometer();
	this.uploadSpeed = speedometer();

	this._destroyed = false;
	this._queues = [fifo()];
	this._peers = {};
};

util.inherits(Swarm, EventEmitter);

Swarm.prototype.__defineGetter__('queued', function() {
	return this._queues.reduce(function(prev, queue) {
		return prev + queue.length;
	}, 0);
});

Swarm.prototype.pause = function() {
	this.paused = true;
};

Swarm.prototype.resume = function() {
	this.paused = false;
	this._drain();
};

Swarm.prototype.priority = function(addr, level) {
	addr = toAddress(addr);
	var peer = this._peers[addr];

	if (!peer) return 0;
	if (typeof level !== 'number' || peer.priority === level) return level;

	if (!this._queues[level]) this._queues[level] = fifo();

	if (peer.node) {
		this._queues[peer.priority].remove(peer.node);
		peer.node = this._queues[level].push(addr);
	}

	return peer.priority = level;
};

Swarm.prototype.add = function(addr) {
	if (this._destroyed || this._peers[addr]) return;

	var port = Number(addr.split(':')[1]);
	if (!(port > 0 && port < 65535)) return;

	this._peers[addr] = {
		node: this._queues[0].push(addr),
		wire: null,
		timeout: null,
		reconnect: false,
		priority: 0,
		retries: 0,
	};

	this._drain();
};

Swarm.prototype.remove = function(addr) {
	this._remove(toAddress(addr));
	this._drain();
};

Swarm.prototype.listen = function(port, onlistening) {
	if (onlistening) this.once('listening', onlistening);
	this.port = port;
	join(this.port, this);
};

Swarm.prototype.destroy = function() {
	this._destroyed = true;

	var self = this;
	Object.keys(this._peers).forEach(function(addr) {
		self._remove(addr);
	});

	leave(this.port, this);
	process.nextTick(function() {
		self.emit('close');
	});
};

Swarm.prototype._remove = function(addr) {
	var peer = this._peers[addr];
	if (!peer) return;
	delete this._peers[addr];
	if (peer.node) this._queues[peer.priority].remove(peer.node);
	if (peer.timeout) clearTimeout(peer.timeout);
	if (peer.wire) peer.wire.destroy();
};

Swarm.prototype._drain = function() {
	if (this.connections.length >= this.size || this.paused) return;

	var self = this;
	var addr = this._shift();
	if (!addr) return;

	var peer = this._peers[addr];
	if (!peer) return;

	var parts = addr.split(':');
	var connection = net.connect(parts[1], parts[0]);
	if (peer.timeout) clearTimeout(peer.timeout);

	peer.node = null;
	peer.timeout = null;

	var wire = onwire(connection, function(infoHash) {
		if (infoHash.toString('hex') !== self.infoHash.toString('hex')) return connection.destroy();
		peer.reconnect = true;
		peer.retries = 0;
		self._onwire(connection, wire);
	});

	var repush = function() {
		peer.node = self._queues[peer.priority].push(addr);
		self._drain();
	};

	wire.on('end', function() {
		peer.wire = null;
		if (!peer.reconnect || self._destroyed || peer.retries >= RECONNECT_WAIT.length) return self._remove(addr);
		peer.timeout = setTimeout(repush, RECONNECT_WAIT[peer.retries++]);
	});

	peer.wire = wire;
	self._onconnection(connection);

	wire.peerAddress = addr;
	wire.handshake(this.infoHash, this.peerId, this.handshake);
};

Swarm.prototype._shift = function() {
	for (var i = this._queues.length-1; i >= 0; i--) {
		if (this._queues[i] && this._queues[i].length) return this._queues[i].shift();
	}
	return null;
};

Swarm.prototype._onincoming = function(connection, wire) {
	wire.peerAddress = connection.address().address + ':' + connection.address().port;
	wire.handshake(this.infoHash, this.peerId, this.handshake);

	this._onconnection(connection);
	this._onwire(connection, wire);
};

Swarm.prototype._onconnection = function(connection) {
	var self = this;

	connection.once('close', function() {
		self.connections.splice(self.connections.indexOf(connection), 1);
		self._drain();
	});

	this.connections.push(connection);
};

Swarm.prototype._onwire = function(connection, wire) {
	var self = this;

	wire.on('download', function(downloaded) {
		self.downloaded += downloaded;
		self.downloadSpeed(downloaded);
		self.emit('download', downloaded);
	});

	wire.on('upload', function(uploaded) {
		self.uploaded += uploaded;
		self.uploadSpeed(uploaded);
		self.emit('upload', uploaded);
	});

	var cleanup = once(function() {
		self.wires.splice(self.wires.indexOf(wire), 1);
		connection.destroy();
	});

	connection.on('close', cleanup);
	connection.on('error', cleanup);
	connection.on('end', cleanup);
	wire.on('end', cleanup);
	wire.on('close', cleanup);
	wire.on('finish', cleanup);

	this.wires.push(wire);
	this.emit('wire', wire, connection);
};

module.exports = Swarm;
<commit_msg>make timeout configurable. see #6<commit_after>var net = require('net');
var fifo = require('fifo');
var once = require('once');
var speedometer = require('speedometer');
var peerWireProtocol = require('peer-wire-protocol');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var HANDSHAKE_TIMEOUT = 25000;
var RECONNECT_WAIT = [1000, 5000, 15000, 30000, 60000, 120000, 300000, 600000];
var DEFAULT_SIZE = 100;

var toBuffer = function(str, encoding) {
	return Buffer.isBuffer(str) ? str : new Buffer(str, encoding);
};

var toAddress = function(wire) {
	if (typeof wire === 'string') return wire;
	return wire.peerAddress;
};

var onwire = function(swarm, connection, onhandshake) {
	var wire = peerWireProtocol();

	connection.on('end', function() {
		connection.destroy();
	});
	connection.on('error', function() {
		connection.destroy();
	});
	connection.on('close', function() {
		wire.destroy();
	});

	var destroy = function() {
		connection.destroy();
	};
	var timeout = setTimeout(destroy, swarm.timeout);

	wire.once('handshake', function(infoHash, peerId) {
		clearTimeout(timeout);
		onhandshake(infoHash, peerId);
	});

	connection.pipe(wire).pipe(connection);
	return wire;
};

var pools = {};

var leave = function(port, swarm) {
	if (!pools[port]) return;
	delete pools[port].swarms[swarm.infoHash.toString('hex')];

	if (Object.keys(pools[port].swarms).length) return;
	pools[port].server.close();
	delete pools[port];
};

var join = function(port, swarm) {
	var pool = pools[port];

	if (!pool) {
		var swarms = {};
		var server = net.createServer(function(connection) {
			var wire = onwire(swarm, connection, function(infoHash, peerId) {
				var swarm = swarms[infoHash.toString('hex')];
				if (!swarm) return connection.destroy();
				swarm._onincoming(connection, wire);
			});
		});

		server.listen(port, function() {
			pool.listening = true;
			Object.keys(swarms).forEach(function(infoHash) {
				swarms[infoHash].emit('listening');
			});
		});

		pool = pools[port] = {
			server: server,
			swarms: swarms,
			listening: false
		};
	}

	var infoHash = swarm.infoHash.toString('hex');

	if (pool.listening) {
		process.nextTick(function() {
			swarm.emit('listening');
		});
	}
	if (pool.swarms[infoHash]) {
		process.nextTick(function() {
			swarm.emit('error', new Error('port and info hash already in use'));
		});
		return;
	}

	pool.swarms[infoHash] = swarm;
};

var Swarm = function(infoHash, peerId, options) {
	if (!(this instanceof Swarm)) return new Swarm(infoHash, peerId, options);
	EventEmitter.call(this);

	options = options || {};
	this.handshake = options.handshake;

	this.port = 0;
	this.size = options.size || DEFAULT_SIZE;
	this.timeout = options.timeout || HANDSHAKE_TIMEOUT;

	this.infoHash = toBuffer(infoHash, 'hex');
	this.peerId = toBuffer(peerId, 'utf-8');

	this.downloaded = 0;
	this.uploaded = 0;
	this.connections = [];
	this.wires = [];
	this.paused = false;

	this.uploaded = 0;
	this.downloaded = 0;

	this.downloadSpeed = speedometer();
	this.uploadSpeed = speedometer();

	this._destroyed = false;
	this._queues = [fifo()];
	this._peers = {};
};

util.inherits(Swarm, EventEmitter);

Swarm.prototype.__defineGetter__('queued', function() {
	return this._queues.reduce(function(prev, queue) {
		return prev + queue.length;
	}, 0);
});

Swarm.prototype.pause = function() {
	this.paused = true;
};

Swarm.prototype.resume = function() {
	this.paused = false;
	this._drain();
};

Swarm.prototype.priority = function(addr, level) {
	addr = toAddress(addr);
	var peer = this._peers[addr];

	if (!peer) return 0;
	if (typeof level !== 'number' || peer.priority === level) return level;

	if (!this._queues[level]) this._queues[level] = fifo();

	if (peer.node) {
		this._queues[peer.priority].remove(peer.node);
		peer.node = this._queues[level].push(addr);
	}

	return peer.priority = level;
};

Swarm.prototype.add = function(addr) {
	if (this._destroyed || this._peers[addr]) return;

	var port = Number(addr.split(':')[1]);
	if (!(port > 0 && port < 65535)) return;

	this._peers[addr] = {
		node: this._queues[0].push(addr),
		wire: null,
		timeout: null,
		reconnect: false,
		priority: 0,
		retries: 0,
	};

	this._drain();
};

Swarm.prototype.remove = function(addr) {
	this._remove(toAddress(addr));
	this._drain();
};

Swarm.prototype.listen = function(port, onlistening) {
	if (onlistening) this.once('listening', onlistening);
	this.port = port;
	join(this.port, this);
};

Swarm.prototype.destroy = function() {
	this._destroyed = true;

	var self = this;
	Object.keys(this._peers).forEach(function(addr) {
		self._remove(addr);
	});

	leave(this.port, this);
	process.nextTick(function() {
		self.emit('close');
	});
};

Swarm.prototype._remove = function(addr) {
	var peer = this._peers[addr];
	if (!peer) return;
	delete this._peers[addr];
	if (peer.node) this._queues[peer.priority].remove(peer.node);
	if (peer.timeout) clearTimeout(peer.timeout);
	if (peer.wire) peer.wire.destroy();
};

Swarm.prototype._drain = function() {
	if (this.connections.length >= this.size || this.paused) return;

	var self = this;
	var addr = this._shift();
	if (!addr) return;

	var peer = this._peers[addr];
	if (!peer) return;

	var parts = addr.split(':');
	var connection = net.connect(parts[1], parts[0]);
	if (peer.timeout) clearTimeout(peer.timeout);

	peer.node = null;
	peer.timeout = null;

	var wire = onwire(swarm, connection, function(infoHash) {
		if (infoHash.toString('hex') !== self.infoHash.toString('hex')) return connection.destroy();
		peer.reconnect = true;
		peer.retries = 0;
		self._onwire(connection, wire);
	});

	var repush = function() {
		peer.node = self._queues[peer.priority].push(addr);
		self._drain();
	};

	wire.on('end', function() {
		peer.wire = null;
		if (!peer.reconnect || self._destroyed || peer.retries >= RECONNECT_WAIT.length) return self._remove(addr);
		peer.timeout = setTimeout(repush, RECONNECT_WAIT[peer.retries++]);
	});

	peer.wire = wire;
	self._onconnection(connection);

	wire.peerAddress = addr;
	wire.handshake(this.infoHash, this.peerId, this.handshake);
};

Swarm.prototype._shift = function() {
	for (var i = this._queues.length-1; i >= 0; i--) {
		if (this._queues[i] && this._queues[i].length) return this._queues[i].shift();
	}
	return null;
};

Swarm.prototype._onincoming = function(connection, wire) {
	wire.peerAddress = connection.address().address + ':' + connection.address().port;
	wire.handshake(this.infoHash, this.peerId, this.handshake);

	this._onconnection(connection);
	this._onwire(connection, wire);
};

Swarm.prototype._onconnection = function(connection) {
	var self = this;

	connection.once('close', function() {
		self.connections.splice(self.connections.indexOf(connection), 1);
		self._drain();
	});

	this.connections.push(connection);
};

Swarm.prototype._onwire = function(connection, wire) {
	var self = this;

	wire.on('download', function(downloaded) {
		self.downloaded += downloaded;
		self.downloadSpeed(downloaded);
		self.emit('download', downloaded);
	});

	wire.on('upload', function(uploaded) {
		self.uploaded += uploaded;
		self.uploadSpeed(uploaded);
		self.emit('upload', uploaded);
	});

	var cleanup = once(function() {
		self.wires.splice(self.wires.indexOf(wire), 1);
		connection.destroy();
	});

	connection.on('close', cleanup);
	connection.on('error', cleanup);
	connection.on('end', cleanup);
	wire.on('end', cleanup);
	wire.on('close', cleanup);
	wire.on('finish', cleanup);

	this.wires.push(wire);
	this.emit('wire', wire, connection);
};

module.exports = Swarm;
<|endoftext|>


<commit_before>/* global Ember, hebeutils, _, google, moment, s */
import DefaultStory from './../../story-types/default-story/component';

export default DefaultStory.extend({
    storyConfig: {
        title: 'Leeds Outside Markets', // (Provide a story title)
        subTitle: 'Statistics for Leeds city, Yeadon, Pudsey and Otley', // (Provide a story subtitle)
        author: 'Ste Allan',

        description: 'A chart showing how market stall occupancy has changed over a 13-month period.', // (Provide a longer description of the story)
        license: 'UK Open Government Licence (OGL v2)', // (Define which license applies to usage of the story)
        dataSourceUrl: 'http://leedsdatamill.org/dataset/leeds-markets', // (Where did the data come from?)
        feedbackEmail: 'support@hebeworks.com', // (Provide an email users can contact about this story)

        height: '3', // (Set the height of the story)
        slider: true, // (Add a horizontal slider to the story)
        scroll: false, // (Should the story vertically scroll its content?)
    },

    loaded: false,

    onInsertElement: function () {
        this.loadData();
    }.on('didInsertElement'),

    loadData: function () {
        var _this = this;
        var query = { $and: [{ "date": { $lte: new Date("2014-03-31T00:00:00Z") } }, { "date": { $gte: new Date("2014-03-01T00:00:00Z") } }] };
        var url = this.get('appSettings.hebeNodeAPI') + '/ldm-market-occupancy?query=' + this.get('appSettings').encodeQuery(query) + '&limit=-1';
        this.getData(url)
            .then(function (data) {
                if (!Ember.isEmpty(data)) {
                    _this.parseData(data);
                }
            });
    },

    parseData: function (data) {
        var _this = this;
        var chartData = [];
        chartData[0] = ['Day', 'Leeds', 'Pudsey', 'Otley', 'Yeadon'];
        // chartData[0] = ['Day', 'Leeds', 'Pudsey', 'Otley', 'Yeadon','tooltip'];
        for (var i = 1; i <= 31; i++) {
            // [1,144,null,12,null];
            chartData[i] = [i, null, null, null, null];
            // chartData[i] = [i,null,null,null,null,moment(new Date("2014/03/"+i)).format('D MMM')];
        }

        var occupancyData = JSON.parse(JSON.stringify(chartData));
        var percentageData = JSON.parse(JSON.stringify(chartData));

        // ================================================================================
        // Calculate daily averages
        // ================================================================================
        function baseLocObj() {
            return {
                "Leeds": [],
                "Pudsey": [],
                "Otley": [],
                "Yeadon": []
            };
        }
        var days = {
            'Mon': baseLocObj(),
            'Tue': baseLocObj(),
            'Wed': baseLocObj(),
            'Thu': baseLocObj(),
            'Fri': baseLocObj(),
            'Sat': baseLocObj(),
            'Sun': baseLocObj()
        };

        var dailyAverages = [chartData[0]];
        data.forEach(function (obj) {
            obj.occupied = obj.stalls_available - obj.stalls_void;
            obj.percent = obj.stalls_void / obj.stalls_available;
            var locationIndex = chartData[0].indexOf(obj.location);
            var dayOfMonth = moment(new Date(obj.date)).date();
            occupancyData[dayOfMonth][locationIndex] = obj.occupied;
            percentageData[dayOfMonth][locationIndex] = obj.percent;

            // Daily averages
            var day = moment(new Date(obj.date)).format("ddd");
            days[day][obj.location].push(obj.occupied);
        });

        function forEachProp(obj, callback) {
            for (var prop in obj) {
                callback(prop, obj[prop]);
            }
        }

        // foreach day
        forEachProp(days, function (day, dayObj) {
            var arr = [day];
            // foreach location
            forEachProp(dayObj, function (loc, locArr) {
                // calculate the average
                var sum = _.reduce(locArr, function (mem, num) { return mem + num; });
                var average = sum / locArr.length;
                arr.push(average);
            });
            dailyAverages.push(arr);
        });
        // ================================================================================

        this.setProperties({
            occupancyData: occupancyData,
            percentageData: percentageData,
            dailyAverages: dailyAverages
        });
        _this.set('loaded', true);
    },

    loadGoogleAPIs: function () {
        // Draw the chart when the APIs have loaded
        google.setOnLoadCallback(
            this.drawTotalOccupiedStalls(),
            this.drawPCEmptyStalls(),
            this.drawAvgOccupiedStalls()
            );
    }.observes('loaded'),

    drawTotalOccupiedStalls: function () {
        var chartData = this.get('occupancyData'),
            data = new google.visualization.DataTable();

        // Original
        // data = google.visualization.arrayToDataTable(chartData);

        // New
        data.addColumn('number', 'Day of Month');
        data.addColumn('number', 'Leeds');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        data.addColumn('number', 'Yeadon');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        data.addColumn('number', 'Otley');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        data.addColumn('number', 'Pudsey');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        var selectedMonth = new Date();
        function htmlTooltip(day, val) {
            var date = moment(s.lpad(day, 2, "0") + moment(selectedMonth).format("/MM/YYYY"), "DD/MM/YYYY");
            date = date.format('ddd') + ' ' + date.format('Do');
            return '
'+date + '
' + val + ' stalls
';
        }

        var rows = _.map(chartData.splice(1), function (row) {
            return [
                row[0], // day
                row[1], // leeds value
                htmlTooltip(row[0], row[1]), // leeds tooltip
                row[2], // yeadon value
                htmlTooltip(row[0], row[2]), // yeadon tooltip
                row[3], // otley value
                htmlTooltip(row[0], row[3]), // otley tooltip
                row[4], // pudsey value
                htmlTooltip(row[0], row[4]) // pudsey tooltip
            ];
        });
        // data.addRows([
        //     [1, 132, 'Leeds tooltip', null, 'Yeadon tooltip', 50, 'Otley tooltip', 3, 'Pudsey tooltip'],
        //     [2, 178, 'Leeds tooltip', 3, 'Yeadon tooltip', 13, 'Otley tooltip', 5, 'Pudsey tooltip'],
        //     [3, 154, 'Leeds tooltip', null, 'Yeadon tooltip', 35, 'Otley tooltip', 10, 'Pudsey tooltip']
        // ]);

        data.addRows(rows);

        var options = {
            title: 'Daily Number of Occupied Stalls',
            legend: {
                position: 'top',
                maxLines: '4'
            },
            tooltip: {
                isHtml: true
            },
            width: 290,
            height: 345,
            pointSize: 5,
            lineWidth: 1,
            interpolateNulls: true,
            chartArea: {
                width: '85%',
                height: '60%',
                top: '20%',
                left: '10%'
            },
            hAxis: {
                title: 'Day of month'
            },
            vAxis: {
                minorGridlines: {
                    count: 4
                }
            },
            crosshair: {
                trigger: 'both'
            }
        };

        var chart = new google.visualization.AreaChart(document.getElementById('chart-no-occupied-stalls'));

        chart.draw(data, options);
    },

    drawPCEmptyStalls: function () {
        var chartData = this.get('percentageData'),
            data = new google.visualization.DataTable();

        // Original
        // data = google.visualization.arrayToDataTable(chartData);

        // New
        data.addColumn('number', 'Day of Month');
        data.addColumn('number', 'Leeds');
        data.addColumn({ type: 'string', role: 'tooltip' });
        data.addColumn('number', 'Yeadon');
        data.addColumn({ type: 'string', role: 'tooltip' });
        data.addColumn('number', 'Otley');
        data.addColumn({ type: 'string', role: 'tooltip' });
        data.addColumn('number', 'Pudsey');
        data.addColumn({ type: 'string', role: 'tooltip' });

        data.addRows([
            [1, .24, 'Leeds tooltip', null, 'Yeadon tooltip', .05, 'Otley tooltip', .2, 'Pudsey tooltip'],
            [2, .33, 'Leeds tooltip', .9, 'Yeadon tooltip', .2, 'Otley tooltip', .15, 'Pudsey tooltip'],
            [3, .10, 'Leeds tooltip', null, 'Yeadon tooltip', .1, 'Otley tooltip', .4, 'Pudsey tooltip']
        ]);

        var options = {
            title: 'Percentage of Stalls Empty',
            legend: {
                position: 'top',
                maxLines: '4'
            },
            width: 290,
            height: 345,
            pointSize: 5,
            lineWidth: 1,
            interpolateNulls: true,
            chartArea: {
                width: '80%',
                height: '60%',
                top: '20%',
                left: '15%'
            },
            hAxis: {
                title: 'Day'
            },
            vAxis: {
                format: 'percent',
                minorGridlines: {
                    count: 4
                }
            },
            crosshair: {
                trigger: 'both'
            },
            tooltip: {
                isHtml: true
            }
        };

        var chart = new google.visualization.AreaChart(document.getElementById('chart-pc-empty-stalls'));

        chart.draw(data, options);
    },

    drawAvgOccupiedStalls: function () {
        var chartData = this.get('dailyAverages');
        var data = new google.visualization.DataTable();
        data = google.visualization.arrayToDataTable(chartData);

        var options = {
            title: 'Daily Avg. Number of Occupied Stalls',
            vAxis: {
                baseline: 0,
                minorGridlines: {
                    count: 4
                }
            },
            chartArea: {
                width: '90%',
                height: '60%',
                top: '20%',
                left: '10%'
            },
            width: 290,
            height: 345,
            legend: {
                position: 'top',
                maxLines: '4'
            },
            isStacked: true,
            hAxis: {
                title: 'Day of the week'
            },
            tooltip: {
                isHtml: true
            }
        };

        var chart = new google.visualization.ColumnChart(
            document.getElementById('chart-avg-occupied-stalls'));

        chart.draw(data, options);
    }
});
<commit_msg>Updated appearance of market occupancy story.<commit_after>/* global Ember, hebeutils, _, google, moment, s */
import DefaultStory from './../../story-types/default-story/component';

export default DefaultStory.extend({
    storyConfig: {
        title: 'Leeds Outside Markets', // (Provide a story title)
        subTitle: 'Statistics for Leeds city, Yeadon, Pudsey and Otley', // (Provide a story subtitle)
        author: 'Ste Allan',

        description: 'A chart showing how market stall occupancy has changed over a 13-month period.', // (Provide a longer description of the story)
        license: 'UK Open Government Licence (OGL v2)', // (Define which license applies to usage of the story)
        dataSourceUrl: 'http://leedsdatamill.org/dataset/leeds-markets', // (Where did the data come from?)
        feedbackEmail: 'support@hebeworks.com', // (Provide an email users can contact about this story)

        height: '3', // (Set the height of the story)
        slider: true, // (Add a horizontal slider to the story)
        scroll: false, // (Should the story vertically scroll its content?)
    },

    loaded: false,

    onInsertElement: function () {
        this.loadData();
    }.on('didInsertElement'),

    loadData: function () {
        var _this = this;
        var query = { $and: [{ "date": { $lte: new Date("2014-03-31T00:00:00Z") } }, { "date": { $gte: new Date("2014-03-01T00:00:00Z") } }] };
        var url = this.get('appSettings.hebeNodeAPI') + '/ldm-market-occupancy?query=' + this.get('appSettings').encodeQuery(query) + '&limit=-1';

        console.log(url);

        this.getData(url)
            .then(function (data) {
                if (!Ember.isEmpty(data)) {
                    _this.parseData(data);
                }
            });
    },

    parseData: function (data) {
        var _this = this;
        var chartData = [];
        chartData[0] = ['Day', 'Leeds', 'Pudsey', 'Otley', 'Yeadon'];
        // chartData[0] = ['Day', 'Leeds', 'Pudsey', 'Otley', 'Yeadon','tooltip'];
        for (var i = 1; i <= 31; i++) {
            // [1,144,null,12,null];
            chartData[i] = [i, null, null, null, null];
            // chartData[i] = [i,null,null,null,null,moment(new Date("2014/03/"+i)).format('D MMM')];
        }

        var occupancyData = JSON.parse(JSON.stringify(chartData));
        var percentageData = JSON.parse(JSON.stringify(chartData));

        // ================================================================================
        // Calculate daily averages
        // ================================================================================
        function baseLocObj() {
            return {
                "Leeds": [],
                "Pudsey": [],
                "Otley": [],
                "Yeadon": []
            };
        }
        var days = {
            'Mon': baseLocObj(),
            'Tue': baseLocObj(),
            'Wed': baseLocObj(),
            'Thu': baseLocObj(),
            'Fri': baseLocObj(),
            'Sat': baseLocObj(),
            'Sun': baseLocObj()
        };

        var dailyAverages = [chartData[0]];
        data.forEach(function (obj) {
            obj.occupied = obj.stalls_available - obj.stalls_void;
            obj.percent = obj.stalls_void / obj.stalls_available;
            var locationIndex = chartData[0].indexOf(obj.location);
            var dayOfMonth = moment(new Date(obj.date)).date();
            occupancyData[dayOfMonth][locationIndex] = obj.occupied;
            percentageData[dayOfMonth][locationIndex] = obj.percent;

            // Daily averages
            var day = moment(new Date(obj.date)).format("ddd");
            days[day][obj.location].push(obj.occupied);
        });

        function forEachProp(obj, callback) {
            for (var prop in obj) {
                callback(prop, obj[prop]);
            }
        }

        // foreach day
        forEachProp(days, function (day, dayObj) {
            var arr = [day];
            // foreach location
            forEachProp(dayObj, function (loc, locArr) {
                // calculate the average
                var sum = _.reduce(locArr, function (mem, num) { return mem + num; });
                var average = sum / locArr.length;
                arr.push(average);
            });
            dailyAverages.push(arr);
        });
        // ================================================================================

        this.setProperties({
            occupancyData: occupancyData,
            percentageData: percentageData,
            dailyAverages: dailyAverages
        });
        _this.set('loaded', true);
    },

    loadGoogleAPIs: function () {
        // Draw the chart when the APIs have loaded
        google.setOnLoadCallback(
            this.drawTotalOccupiedStalls(),
            this.drawPCEmptyStalls(),
            this.drawAvgOccupiedStalls()
            );
    }.observes('loaded'),

    drawTotalOccupiedStalls: function () {
        var chartData = this.get('occupancyData'),
            data = new google.visualization.DataTable();

        // Original
        // data = google.visualization.arrayToDataTable(chartData);

        // New
        data.addColumn('number', 'Day of Month');
        data.addColumn('number', 'Leeds');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        data.addColumn('number', 'Yeadon');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        data.addColumn('number', 'Otley');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        data.addColumn('number', 'Pudsey');
        data.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': true } });
        var selectedMonth = new Date();
        function htmlTooltip(day, val) {
            var date = moment(s.lpad(day, 2, "0") + moment(selectedMonth).format("/MM/YYYY"), "DD/MM/YYYY");
            date = date.format('ddd') + ' ' + date.format('Do');
            return '
'+date + '
' + val + ' stalls
';
        }

        var rows = _.map(chartData.splice(1), function (row) {
            return [
                row[0], // day
                row[1], // leeds value
                htmlTooltip(row[0], row[1]), // leeds tooltip
                row[2], // yeadon value
                htmlTooltip(row[0], row[2]), // yeadon tooltip
                row[3], // otley value
                htmlTooltip(row[0], row[3]), // otley tooltip
                row[4], // pudsey value
                htmlTooltip(row[0], row[4]) // pudsey tooltip
            ];
        });
        // data.addRows([
        //     [1, 132, 'Leeds tooltip', null, 'Yeadon tooltip', 50, 'Otley tooltip', 3, 'Pudsey tooltip'],
        //     [2, 178, 'Leeds tooltip', 3, 'Yeadon tooltip', 13, 'Otley tooltip', 5, 'Pudsey tooltip'],
        //     [3, 154, 'Leeds tooltip', null, 'Yeadon tooltip', 35, 'Otley tooltip', 10, 'Pudsey tooltip']
        // ]);

        data.addRows(rows);

        var options = {
            title: 'Daily Number of Occupied Stalls',
            legend: {
                position: 'top',
                maxLines: '4'
            },
            tooltip: {
                isHtml: true
            },
            width: 290,
            height: 345,
            pointSize: 0,
            lineWidth: 2,
            interpolateNulls: true,
            chartArea: {
                width: '85%',
                height: '60%',
                top: '20%',
                left: '10%'
            },
            hAxis: {
                title: 'Day of month'
            },
            vAxis: {
                minorGridlines: {
                    count: 4
                }
            },
            crosshair: {
                trigger: 'both'
            },
            series: {
                2: {
                    color: '#f8e71c'
                },
                3: {
                    color: 'green'
                }
            },
            areaOpacity: 0.5
        };

        var chart = new google.visualization.AreaChart(document.getElementById('chart-no-occupied-stalls'));

        chart.draw(data, options);
    },

    drawPCEmptyStalls: function () {
        console.log(this.percentageData);

        var chartData = this.get('percentageData');
            // data = new google.visualization.DataTable();

        // Original
        var data = google.visualization.arrayToDataTable(chartData);

        // New
        // data.addColumn('number', 'Day of Month');
        // data.addColumn('number', 'Leeds');
        // data.addColumn({ type: 'string', role: 'tooltip' });
        // data.addColumn('number', 'Yeadon');
        // data.addColumn({ type: 'string', role: 'tooltip' });
        // data.addColumn('number', 'Otley');
        // data.addColumn({ type: 'string', role: 'tooltip' });
        // data.addColumn('number', 'Pudsey');
        // data.addColumn({ type: 'string', role: 'tooltip' });

        // data.addRows(chartData);

        var options = {
            title: 'Percentage of Stalls Empty',
            legend: {
                position: 'top',
                maxLines: '4'
            },
            width: 290,
            height: 345,
            pointSize: 0,
            lineWidth: 2,
            interpolateNulls: true,
            chartArea: {
                width: '80%',
                height: '60%',
                top: '20%',
                left: '15%'
            },
            hAxis: {
                title: 'Day',
                format: 'short'
            },
            vAxis: {
                format: 'percent',
                minorGridlines: {
                    count: 4
                }
            },
            crosshair: {
                trigger: 'both'
            },
            tooltip: {
                // isHtml: true
            },
            series: {
                2: {
                    color: '#f8e71c'
                },
                3: {
                    color: 'green'
                }
            },
            areaOpacity: 0.5
        };

        var chart = new google.visualization.AreaChart(document.getElementById('chart-pc-empty-stalls'));

        chart.draw(data, options);
    },

    drawAvgOccupiedStalls: function () {
        var chartData = this.get('dailyAverages');
        var data = new google.visualization.DataTable();
        data = google.visualization.arrayToDataTable(chartData);

        var options = {
            title: 'Daily Avg. Number of Occupied Stalls',
            vAxis: {
                baseline: 0,
                minorGridlines: {
                    count: 4
                }
            },
            chartArea: {
                width: '90%',
                height: '60%',
                top: '20%',
                left: '10%'
            },
            width: 290,
            height: 345,
            legend: {
                position: 'top',
                maxLines: '4'
            },
            isStacked: true,
            hAxis: {
                title: 'Day of the week'
            },
            tooltip: {
                isHtml: true
            },
            series: {
                2: {
                    color: '#f8e71c'
                },
                3: {
                    color: 'green'
                }
            },
        };

        var chart = new google.visualization.ColumnChart(
            document.getElementById('chart-avg-occupied-stalls'));

        chart.draw(data, options);
    }
});
<|endoftext|>


<commit_before>'use strict';

var async = require('async');
var fmt   = require('util').format;
var knex  = require('knex');
var pg    = require('pg');

pg.on('end', function onPgEnd() {
  LivePg.willClose = true;
});

/**
 * Get a livedb client for connecting to a PostgreSQL database.
 *
 * @classdesc A PostgreSQL adapter for livedb
 * @class
 * @param {string} conn a PostgreSQL connection URL
 * @param {string} table a database table name
 */
function LivePg(conn, table) {
  this.conn  = conn;
  this.db    = knex({ client: 'pg', connection: conn });
  this.table = table;
}

/*
 * SNAPSHOT API
 * ============
 */

/**
 * A callback called when getting a document snapshot.
 *
 * @callback LivePg~getSnapshotCallback
 * @param {?Error} err an error
 * @param {?Object} doc document data
 */
/**
 * Get a document snapshot from a given collection.
 *
 * @method
 * @param {string} cName the collection name
 * @param {string} docName the document name
 * @param {LivePg~getSnapshotCallback} cb a callback called with the document
 */
LivePg.prototype.getSnapshot = function getSnapshot(cName, docName, cb) {
  this.db(this.table)
    .where({ collection: cName, name: docName })
    .select('data')
    .limit(1)
    .exec(function onResult(err, rows) {
      if (err) return cb(err, null);
      cb(null, rows.length ? rows[0].data : null);
    });
};

/**
 * A callback called when writing a document snapshot.
 *
 * @callback LivePg~writeSnapshotCallback
 * @param {?Error} err an error
 * @param {?Object} doc document data
 */
/**
 * Write a document snapshot to a given collection.
 *
 * This method uses pg instead of knex because of the complex transaction and
 * locking that's necessary. The lock prevents a race condition between a failed
 * `UPDATE` and the subsequent `INSERT`.
 *
 * @method
 * @param {string} cName the collection name
 * @param {string} docName the document name
 * @param {Object} data the document data
 * @param {LivePg~writeSnapshotCallback} cb a callback called with the document
 */
LivePg.prototype.writeSnapshot = function writeSnapshot(cName, docName, data, cb) {
  var conn  = this.conn;
  var table = this.table;
  var client, done;

  async.waterfall([
    connect,
    begin,
    lock,
    upsert,
    commit
  ], function onDone(err) {
    if (done) done();
    if (err) return cb(err);
    cb(null, data);
  });

  function connect(callback) {
    pg.connect(conn, callback);
  }

  function begin(_client, _done, callback) {
    client = _client;
    done   = _done;
    client.query('BEGIN;', callback);
  }

  function lock(res, callback) {
    var _table = client.escapeIdentifier(table);
    var query  = fmt('LOCK TABLE %s IN SHARE ROW EXCLUSIVE MODE;', _table);
    client.query(query, callback);
  }

  function upsert(res, callback) {
    var _table   = client.escapeIdentifier(table);

    var update = fmt('UPDATE %s SET data = $1 ' +
      'WHERE collection = $2 AND name = $3', _table);

    var insert = fmt('INSERT INTO %s (collection, name, data) ' +
      'SELECT $2, $3, $1', _table);

    var query = fmt('WITH upsert AS (%s RETURNING *) %s ' +
      'WHERE NOT EXISTS (SELECT * FROM upsert);', update, insert);

    client.query(query, [data, cName, docName], callback);
  }

  function commit(res, callback) {
    client.query('COMMIT;', callback);
  }
};

/**
 * A callback called when getting snapshots in bulk.
 *
 * @callback LivePg~bulkGetSnapshotCallback
 * @param {?Error} err an error
 * @param {?Object} results the results
 */
/**
 * Get specific documents from multiple collections.
 *
 * @method
 * @param {Object} requests the requests documents
 * @param {LivePg~bulkGetSnapshotCallback} cb a callback called with the results
 */
LivePg.prototype.bulkGetSnapshot = function bulkGetSnapshot(requests, cb) {
  var collections = Object.keys(requests);

  async.parallel(collections.map(function eachCollection(cName) {
    var docNames = requests[cName];

    return function getFromCollection(callback) {
      this.fromCollection(cName, docNames, callback);
    }.bind(this);
  }.bind(this)), function onDone(err, results) {
    if (err) return cb(err);
    cb(null, results.reduce(function eachResult(obj, result, i) {
      obj[collections[i]] = result;
      return obj;
    }, {}));
  });
};

/**
 * A callback called when getting snapshots for a given collection.
 *
 * @callback LivePg~fromCollectionCallback
 * @private
 * @param {?Error} err an error
 * @param {?Object} results an set of results for the found documents
 */
/**
 * Get specific documents from a given collection.
 *
 * @method
 * @private
 * @param {string} cName the collection name
 * @param {Array} docNames the document names
 * @param {LivePg~fromCollectionCallback} cb a callback called with results
 */
LivePg.prototype.fromCollection = function fromCollection(cName, docNames, cb) {
  this.db(this.table)
    .whereIn('name', docNames)
    .andWhere({ collection: cName })
    .select('name', 'data')
    .exec(function onResult(err, results) {
      if (err) return cb(err, null);
      cb(null, results.reduce(function eachResult(obj, result) {
        obj[result.name] = result.data;
        return obj;
      }, {}));
    });
};

/*
 * OPERATION API
 * =============
 */

/**
 * A callback called when writing an operation
 *
 * @callback LivePg~writeOpCallback
 * @param {?Error} err an error
 * @param {?Object} op the operation data
 */
/**
 * Write an operation.
 *
 * @method
 * @param {string} cName a collection name
 * @param {string} docName a document name
 * @param {Object} opData the operation data
 * @param {LivePg~writeOpCallback} cb a callback called with the op data
 */
LivePg.prototype.writeOp = function writeOp(cName, docName, opData, cb) {
  this.db(this.table)
    .insert({
      collection_name: cName,
      document_name  : docName,
      version        : opData.v,
      data           : opData
    })
    .returning('data')
    .exec(function onResult(err, rows) {
      if (err) return cb(err, null);
      cb(null, rows.length ? rows[0] : null);
    });
};

/**
 * A callback called with the next version of the document
 *
 * @callback LivePg~getVersionCallback
 * @param {?Error} err an error
 * @param {?number} version the next document version
 */
/**
 * Get the next document version.
 *
 * @method
 * @param {string} cName a collection name
 * @param {string} docName a document name
 * @param {LivePg~getVersionCallback} cb a callback called with the next version
 */
LivePg.prototype.getVersion = function getVersion(cName, docName, cb) {
  this.db(this.table)
    .where({ collection_name: cName, document_name: docName })
    .select('version')
    .orderBy('version', 'desc')
    .limit(1)
    .exec(function onResult(err, rows) {
      if (err) return cb(err);
      cb(null, rows.length ? parseInt(rows[0].version, 10) + 1 : 0);
    });
};

/**
 * A callback called with ops
 *
 * @callback LivePg~getOpsCallback
 * @param {?Error} err an error
 * @param {?Array(Object)} ops the requested ops
 */
/**
 * Get operations between `start` and `end`, noninclusively.
 *
 * @method
 * @param {string} cName a collection name
 * @param {string} docName a document name
 * @param {number} start the start version
 * @param {end} end the end version
 * @param {LivePg~getOpsCallback} cb a callback called with the ops
 */
LivePg.prototype.getOps = function getOps(cName, docName, start, end, cb) {
  var query = this.db(this.table).where('version', '>=', start);

  if (typeof end === 'number') {
    query.andWhere('version', '<', end);
  }

  query.select('data')
    .orderBy('version', 'asc')
    .exec(function onResult(err, rows) {
      if (err) return cb(err);
      cb(null, rows.map(function eachRow(row) {
        return row.data;
      }));
    });
};

/**
 * Close the connection to the database.
 *
 * @method
 * @param {LivePg~closeCallback} cb a callback called when the connection closes
 */
LivePg.prototype.close = function close(cb) {
  LivePg.close(cb);
};

/**
 * A callback called when the database connection has closed
 *
 * @callback LivePg~closeCallback
 * @static
 */
/**
 * Close the connection to the database.
 * @method
 * @static
 * @param {LivePg~closeCallback} cb a callback called when the connection closes
 */
LivePg.close = function close(cb) {
  if (this.willClose) {
    cb();
  } else {
    pg.on('end', function onPgEnd() {
      cb();
    });

    pg.end();
  }
};

module.exports = LivePg;
<commit_msg>Document `end` param in `#getOps` as nullable<commit_after>'use strict';

var async = require('async');
var fmt   = require('util').format;
var knex  = require('knex');
var pg    = require('pg');

pg.on('end', function onPgEnd() {
  LivePg.willClose = true;
});

/**
 * Get a livedb client for connecting to a PostgreSQL database.
 *
 * @classdesc A PostgreSQL adapter for livedb
 * @class
 * @param {string} conn a PostgreSQL connection URL
 * @param {string} table a database table name
 */
function LivePg(conn, table) {
  this.conn  = conn;
  this.db    = knex({ client: 'pg', connection: conn });
  this.table = table;
}

/*
 * SNAPSHOT API
 * ============
 */

/**
 * A callback called when getting a document snapshot.
 *
 * @callback LivePg~getSnapshotCallback
 * @param {?Error} err an error
 * @param {?Object} doc document data
 */
/**
 * Get a document snapshot from a given collection.
 *
 * @method
 * @param {string} cName the collection name
 * @param {string} docName the document name
 * @param {LivePg~getSnapshotCallback} cb a callback called with the document
 */
LivePg.prototype.getSnapshot = function getSnapshot(cName, docName, cb) {
  this.db(this.table)
    .where({ collection: cName, name: docName })
    .select('data')
    .limit(1)
    .exec(function onResult(err, rows) {
      if (err) return cb(err, null);
      cb(null, rows.length ? rows[0].data : null);
    });
};

/**
 * A callback called when writing a document snapshot.
 *
 * @callback LivePg~writeSnapshotCallback
 * @param {?Error} err an error
 * @param {?Object} doc document data
 */
/**
 * Write a document snapshot to a given collection.
 *
 * This method uses pg instead of knex because of the complex transaction and
 * locking that's necessary. The lock prevents a race condition between a failed
 * `UPDATE` and the subsequent `INSERT`.
 *
 * @method
 * @param {string} cName the collection name
 * @param {string} docName the document name
 * @param {Object} data the document data
 * @param {LivePg~writeSnapshotCallback} cb a callback called with the document
 */
LivePg.prototype.writeSnapshot = function writeSnapshot(cName, docName, data, cb) {
  var conn  = this.conn;
  var table = this.table;
  var client, done;

  async.waterfall([
    connect,
    begin,
    lock,
    upsert,
    commit
  ], function onDone(err) {
    if (done) done();
    if (err) return cb(err);
    cb(null, data);
  });

  function connect(callback) {
    pg.connect(conn, callback);
  }

  function begin(_client, _done, callback) {
    client = _client;
    done   = _done;
    client.query('BEGIN;', callback);
  }

  function lock(res, callback) {
    var _table = client.escapeIdentifier(table);
    var query  = fmt('LOCK TABLE %s IN SHARE ROW EXCLUSIVE MODE;', _table);
    client.query(query, callback);
  }

  function upsert(res, callback) {
    var _table   = client.escapeIdentifier(table);

    var update = fmt('UPDATE %s SET data = $1 ' +
      'WHERE collection = $2 AND name = $3', _table);

    var insert = fmt('INSERT INTO %s (collection, name, data) ' +
      'SELECT $2, $3, $1', _table);

    var query = fmt('WITH upsert AS (%s RETURNING *) %s ' +
      'WHERE NOT EXISTS (SELECT * FROM upsert);', update, insert);

    client.query(query, [data, cName, docName], callback);
  }

  function commit(res, callback) {
    client.query('COMMIT;', callback);
  }
};

/**
 * A callback called when getting snapshots in bulk.
 *
 * @callback LivePg~bulkGetSnapshotCallback
 * @param {?Error} err an error
 * @param {?Object} results the results
 */
/**
 * Get specific documents from multiple collections.
 *
 * @method
 * @param {Object} requests the requests documents
 * @param {LivePg~bulkGetSnapshotCallback} cb a callback called with the results
 */
LivePg.prototype.bulkGetSnapshot = function bulkGetSnapshot(requests, cb) {
  var collections = Object.keys(requests);

  async.parallel(collections.map(function eachCollection(cName) {
    var docNames = requests[cName];

    return function getFromCollection(callback) {
      this.fromCollection(cName, docNames, callback);
    }.bind(this);
  }.bind(this)), function onDone(err, results) {
    if (err) return cb(err);
    cb(null, results.reduce(function eachResult(obj, result, i) {
      obj[collections[i]] = result;
      return obj;
    }, {}));
  });
};

/**
 * A callback called when getting snapshots for a given collection.
 *
 * @callback LivePg~fromCollectionCallback
 * @private
 * @param {?Error} err an error
 * @param {?Object} results an set of results for the found documents
 */
/**
 * Get specific documents from a given collection.
 *
 * @method
 * @private
 * @param {string} cName the collection name
 * @param {Array} docNames the document names
 * @param {LivePg~fromCollectionCallback} cb a callback called with results
 */
LivePg.prototype.fromCollection = function fromCollection(cName, docNames, cb) {
  this.db(this.table)
    .whereIn('name', docNames)
    .andWhere({ collection: cName })
    .select('name', 'data')
    .exec(function onResult(err, results) {
      if (err) return cb(err, null);
      cb(null, results.reduce(function eachResult(obj, result) {
        obj[result.name] = result.data;
        return obj;
      }, {}));
    });
};

/*
 * OPERATION API
 * =============
 */

/**
 * A callback called when writing an operation
 *
 * @callback LivePg~writeOpCallback
 * @param {?Error} err an error
 * @param {?Object} op the operation data
 */
/**
 * Write an operation.
 *
 * @method
 * @param {string} cName a collection name
 * @param {string} docName a document name
 * @param {Object} opData the operation data
 * @param {LivePg~writeOpCallback} cb a callback called with the op data
 */
LivePg.prototype.writeOp = function writeOp(cName, docName, opData, cb) {
  this.db(this.table)
    .insert({
      collection_name: cName,
      document_name  : docName,
      version        : opData.v,
      data           : opData
    })
    .returning('data')
    .exec(function onResult(err, rows) {
      if (err) return cb(err, null);
      cb(null, rows.length ? rows[0] : null);
    });
};

/**
 * A callback called with the next version of the document
 *
 * @callback LivePg~getVersionCallback
 * @param {?Error} err an error
 * @param {?number} version the next document version
 */
/**
 * Get the next document version.
 *
 * @method
 * @param {string} cName a collection name
 * @param {string} docName a document name
 * @param {LivePg~getVersionCallback} cb a callback called with the next version
 */
LivePg.prototype.getVersion = function getVersion(cName, docName, cb) {
  this.db(this.table)
    .where({ collection_name: cName, document_name: docName })
    .select('version')
    .orderBy('version', 'desc')
    .limit(1)
    .exec(function onResult(err, rows) {
      if (err) return cb(err);
      cb(null, rows.length ? parseInt(rows[0].version, 10) + 1 : 0);
    });
};

/**
 * A callback called with ops
 *
 * @callback LivePg~getOpsCallback
 * @param {?Error} err an error
 * @param {?Array(Object)} ops the requested ops
 */
/**
 * Get operations between `start` and `end`, noninclusively.
 *
 * @method
 * @param {string} cName a collection name
 * @param {string} docName a document name
 * @param {number} start the start version
 * @param {?end} end the end version
 * @param {LivePg~getOpsCallback} cb a callback called with the ops
 */
LivePg.prototype.getOps = function getOps(cName, docName, start, end, cb) {
  var query = this.db(this.table).where('version', '>=', start);

  if (typeof end === 'number') {
    query.andWhere('version', '<', end);
  }

  query.select('data')
    .orderBy('version', 'asc')
    .exec(function onResult(err, rows) {
      if (err) return cb(err);
      cb(null, rows.map(function eachRow(row) {
        return row.data;
      }));
    });
};

/**
 * Close the connection to the database.
 *
 * @method
 * @param {LivePg~closeCallback} cb a callback called when the connection closes
 */
LivePg.prototype.close = function close(cb) {
  LivePg.close(cb);
};

/**
 * A callback called when the database connection has closed
 *
 * @callback LivePg~closeCallback
 * @static
 */
/**
 * Close the connection to the database.
 * @method
 * @static
 * @param {LivePg~closeCallback} cb a callback called when the connection closes
 */
LivePg.close = function close(cb) {
  if (this.willClose) {
    cb();
  } else {
    pg.on('end', function onPgEnd() {
      cb();
    });

    pg.end();
  }
};

module.exports = LivePg;
<|endoftext|>