<commit_before>var VeryCollection = require('./collection');
var VeryValidator = require('./validator');
var Types = require('./definition').Types;
var underscore = require('underscore');

/* VeryModel, for spawning models out of a definition
 * Instances of VeryModel are factories for models themselves.
 */
function VeryModel(definition, args) {
    args = args || {};
    if (Array.isArray(definition)) {
        this.definition = [];
    } else {
        this.definition = {};
    }
    this.controllers = {};

    //initialize sub model definitions recursively
    this.fields = [];
    this.addDefinition(definition);
}

(function () {

    this.addDefinition = function (defs) {
        var newfields = Object.keys(defs);
        if (!Array.isArray(this.definition)) {
            this.definition = underscore.extend(this.definition, defs);
        } else {
            this.definition = this.definition.concat(defs);
        }
        this.fields = underscore.union(newfields, this.fields);
        newfields.forEach(function (field) {
            this.setupField(field);
        }.bind(this));
    };

    this.inherit = function (parentModel) {
        var fields = parentModel.fields;

        fields.forEach(function _inheritField(field) {
            if (this.definition[field]) return;

            this.fields.push(field);
            this.definition[field] = parentModel.definition[field];

            this.setupField(field);
        }.bind(this));
    };

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

    /* create (optionally with the value object)
     * generates a model for populating, reading, and validating
     * the object over time
     */
    this.create = function (value, ctx) {
        var model = {};
        model.ctx = ctx;

        var meta = model.__verymeta = {
            model: this,
            defs: this.definition,
            data: {},
            map: {},
            reverseMap: {},
            primaryKey: null,
            creating: true,
            isEmpty: true
        };

        if (Array.isArray(meta.defs)) {
            meta.data = [];
        }

        //run through the definition fields
        this.fields.forEach(function _createField(field) {

            //hidden value attribute accessor
            if (typeof meta.defs[field].keyword !== 'undefined') {
                meta.map[meta.defs[field].keyword] = field;
                meta.reverseMap[field] = meta.defs[field].keyword;
                model.__defineGetter__(meta.defs[field].keyword, function () {
                    return model[meta.map[meta.defs[field].keyword]];
                });
            }

            if (meta.defs[field].primary === true) {
                meta.primaryKey = field;
            }

            model.__defineGetter__(field, function () {
                var meta = this.__verymeta;
                if (meta.defs[field].hasOwnProperty('derive')) {
                    meta.data[field] = meta.defs[field].derive(this);
                }
                if (meta.defs[field].hasOwnProperty('processOut') && typeof meta.defs[field].processOut === 'function') {
                    meta.data[field] = meta.defs[field].processOut(meta.data[field]);
                }
                return meta.data[field];
            }.bind(model));

            //handle assignment to sub models, sub arraymodels, and values differently
            if (meta.defs[field].hasOwnProperty('model')) {
                //recursively instatiate submodel
                meta.data[field] = meta.defs[field].subModel.create();
                meta.data[field].__verymeta.parent = model;

                //assigning to the submodel should call loadData on it
                model.__defineSetter__(field, function (value) {
                    this.__verymeta.data[field].loadData(value);
                });

                return;
            }

            if (meta.defs[field].hasOwnProperty('collection')) {
                //create an array-like object for a collection of the sub model
                meta.data[field] = new VeryCollection(meta.defs[field].subVeryCollection, model);

                return;
            }

            //assign the default value to required fields
            //this could get overwritten later at the loadData phase
            if (meta.defs[field].required) {
                if (typeof meta.defs[field].default === 'function') {
                    meta.data[field] = meta.defs[field].default(model);
                } else {
                    meta.data[field] = meta.defs[field].default;
                }
            }

            //validate values as they come in
            //hidden value style setter
            model.__defineSetter__(field, function (value) {
                var meta = this.__verymeta;

                if (meta.defs[field].static === true && !meta.creating) {
                    throw new Error('Cannot set static values');
                }

                if (meta.defs[field].hasOwnProperty('processIn') && typeof meta.defs[field].processIn === 'function') {
                    meta.data[field] = meta.defs[field].processIn(value);
                } else {
                    meta.data[field] = value;
                }

                meta.isEmpty = false;
            });
        }.bind(this));

        //extend controllers
        Object.keys(this.controllers).forEach(function (controller) {
            model[controller] = this.controllers[controller];
        }.bind(this));

        //load data en masse into the model
        //works recursively
        model.loadData = function (value) {
            var meta = this.__verymeta;

            if (Array.isArray(meta.defs)) {
                meta.data = [];
                var valueoff = 0;
                for (var vidx in meta.defs) {
                    if (!meta.defs[vidx].required && value.length + valueoff < meta.defs.length) {
                        if (meta.defs[vidx].default === 'function') {
                            this[vidx] = meta.defs[vidx].default(this);
                        } else {
                            this[vidx] = meta.defs[vidx].default;
                        }
                        valueoff += 1;
                    } else {
                        this[vidx] = value[vidx - valueoff];
                    }
                }
            } else {
                Object.keys(value).forEach(function (key) {
                    if (!meta.defs.hasOwnProperty(key)) return;

                    if (meta.defs[key].hasOwnProperty('collection')) {
                        for (var vidx in value[key]) {
                            meta.isEmpty = false;
                            meta.data[key].push(value[key][vidx]);
                        }
                    } else {
                        meta.isEmpty = false;
                        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 || {};

            var meta = this.__verymeta;
            var obj;

            if (Array.isArray(meta.defs) && !opts.useKeywords) {
                obj = [];
            } else {
                obj = {};
            }

            Object.keys(meta.defs).forEach(function (field) {
                var key = field;
                if (meta.defs[field].private && !opts.withPrivate) {
                    return;
                }

                if (opts.useKeywords && meta.reverseMap.hasOwnProperty(field)) {
                    key = meta.reverseMap[field];
                }

                if (meta.defs[field].hasOwnProperty('model')) {
                    if (!meta.data[field].__verymeta.isEmpty) {
                        obj[key] = meta.data[field].toObject();
                    }
                    return;
                }

                if (meta.defs[field].hasOwnProperty('collection')) {
                    obj[key] = [];
                    meta.data[field].forEach(function (inst) {
                        if (!inst.__verymeta.isEmpty) {
                            obj[key].push(inst.toObject());
                        }
                    });
                    if (obj[key].length === 0) {
                        delete obj[key];
                    }

                    return;
                }

                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 meta = this.__verymeta;
            var errors = [];

            Object.keys(meta.defs).forEach(function (field) {
                var fidx;
                var merrors;
                //if field is set and dependency is set
                if (meta.data.hasOwnProperty(field) && meta.defs[field].depends) {
                    for (fidx in meta.defs[field].depends) {
                        //check to see if other field is set
                        if (!meta.data.hasOwnProperty(fidx) || meta.data[fidx] === undefined) {
                            errors.push(field + ": Dependency -> " + fidx + ": not set");
                        //check to see if dependency has a validator
                        } else if (meta.defs[field].depends instanceof VeryValidator) {
                            merrors = meta.defs[field].depends[fidx].validate(meta.data[fidx]);
                            merrors.forEach(function (error) {
                                errors.push(field + ": Dependency  -> " + fidx + ": " + error);
                            });
                        }
                    }
                }
                //if field is reuqired and it's not set
                if (meta.defs[field].required && (!meta.data.hasOwnProperty(field) || meta.data[field] === undefined)) {
                    errors.push(field + ": required");
                }
                //if this field is another model
                //recursively validate it
                //add errors to our own
                //only if the submodule is required or has been populated
                if (meta.defs[field].hasOwnProperty('model') && meta.data.hasOwnProperty(field) && (!meta.data[field].__verymeta.isEmpty || meta.defs[field].required)) {
                    merrors = meta.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 (meta.defs[field].hasOwnProperty('collection') && meta.data.hasOwnProperty(field)) {
                    var arrayerrors = [];
                    var idx = 0;
                    meta.data[field].forEach(function (model) {
                        //is this collection required? has the model been populated?
                        if (meta.defs[field].required || !model.__verymeta.isEmpty) {
                            merrors = model.doValidate();
                            for (var eidx in merrors) {
                                merrors[eidx] = field + '[' + idx + '].' + merrors[eidx];
                            }
                            errors = errors.concat(merrors);
                        }
                        idx += 1;
                    }.bind(this));
                //if we have a VeryValidator to validate against, validate it
                } else if (meta.data.hasOwnProperty(field) && meta.defs[field].hasOwnProperty('type')) {
                    if (typeof meta.defs[field].type === 'string' && Types.hasOwnProperty(meta.defs[field].type)) {
                        meta.defs[field].type = Types[meta.defs[field].type];
                    }
                    merrors = meta.defs[field].type.validate(meta.data[field]);
                    if (typeof merrors === 'boolean') {
                        if (merrors) {
                            merrors = [];
                        } else {
                            merrors = ["Failed."];
                        }
                    }
                    merrors.forEach(function (error) {
                        errors.push(field + ": " + error);
                    });
                } else if (meta.data.hasOwnProperty(field) && meta.defs[field].hasOwnProperty('array')) {
                    var tidx = 0;
                    meta.defs[field].array.forEach(function (type) {
                        merrors = type.validate(meta.data[field][tidx]);
                        merrors.forEach(function (error) {
                            errors.push(field + "[" + tidx + "]: " + error);
                        });
                        tidx++;
                    }.bind(this));
                }

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

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

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

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

        meta.creating = false;

        return model;
    };

    this.extendModel = function (controllers) {
        this.controllers = controllers;
    };

    this.addModelVar = function (name, value) {
        this.controllers[name] = value;
    };

}).call(VeryModel.prototype);

module.exports = VeryModel;
<commit_msg>make sure extendModel doesn't blow away your shit, dog<commit_after>var VeryCollection = require('./collection');
var VeryValidator = require('./validator');
var Types = require('./definition').Types;
var underscore = require('underscore');

/* VeryModel, for spawning models out of a definition
 * Instances of VeryModel are factories for models themselves.
 */
function VeryModel(definition, args) {
    args = args || {};
    if (Array.isArray(definition)) {
        this.definition = [];
    } else {
        this.definition = {};
    }
    this.controllers = {};

    //initialize sub model definitions recursively
    this.fields = [];
    this.addDefinition(definition);
}

(function () {

    this.addDefinition = function (defs) {
        var newfields = Object.keys(defs);
        if (!Array.isArray(this.definition)) {
            this.definition = underscore.extend(this.definition, defs);
        } else {
            this.definition = this.definition.concat(defs);
        }
        this.fields = underscore.union(newfields, this.fields);
        newfields.forEach(function (field) {
            this.setupField(field);
        }.bind(this));
    };

    this.inherit = function (parentModel) {
        var fields = parentModel.fields;

        fields.forEach(function _inheritField(field) {
            if (this.definition[field]) return;

            this.fields.push(field);
            this.definition[field] = parentModel.definition[field];

            this.setupField(field);
        }.bind(this));
    };

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

    /* create (optionally with the value object)
     * generates a model for populating, reading, and validating
     * the object over time
     */
    this.create = function (value, ctx) {
        var model = {};
        model.ctx = ctx;

        var meta = model.__verymeta = {
            model: this,
            defs: this.definition,
            data: {},
            map: {},
            reverseMap: {},
            primaryKey: null,
            creating: true,
            isEmpty: true
        };

        if (Array.isArray(meta.defs)) {
            meta.data = [];
        }

        //run through the definition fields
        this.fields.forEach(function _createField(field) {

            //hidden value attribute accessor
            if (typeof meta.defs[field].keyword !== 'undefined') {
                meta.map[meta.defs[field].keyword] = field;
                meta.reverseMap[field] = meta.defs[field].keyword;
                model.__defineGetter__(meta.defs[field].keyword, function () {
                    return model[meta.map[meta.defs[field].keyword]];
                });
            }

            if (meta.defs[field].primary === true) {
                meta.primaryKey = field;
            }

            model.__defineGetter__(field, function () {
                var meta = this.__verymeta;
                if (meta.defs[field].hasOwnProperty('derive')) {
                    meta.data[field] = meta.defs[field].derive(this);
                }
                if (meta.defs[field].hasOwnProperty('processOut') && typeof meta.defs[field].processOut === 'function') {
                    meta.data[field] = meta.defs[field].processOut(meta.data[field]);
                }
                return meta.data[field];
            }.bind(model));

            //handle assignment to sub models, sub arraymodels, and values differently
            if (meta.defs[field].hasOwnProperty('model')) {
                //recursively instatiate submodel
                meta.data[field] = meta.defs[field].subModel.create();
                meta.data[field].__verymeta.parent = model;

                //assigning to the submodel should call loadData on it
                model.__defineSetter__(field, function (value) {
                    this.__verymeta.data[field].loadData(value);
                });

                return;
            }

            if (meta.defs[field].hasOwnProperty('collection')) {
                //create an array-like object for a collection of the sub model
                meta.data[field] = new VeryCollection(meta.defs[field].subVeryCollection, model);

                return;
            }

            //assign the default value to required fields
            //this could get overwritten later at the loadData phase
            if (meta.defs[field].required) {
                if (typeof meta.defs[field].default === 'function') {
                    meta.data[field] = meta.defs[field].default(model);
                } else {
                    meta.data[field] = meta.defs[field].default;
                }
            }

            //validate values as they come in
            //hidden value style setter
            model.__defineSetter__(field, function (value) {
                var meta = this.__verymeta;

                if (meta.defs[field].static === true && !meta.creating) {
                    throw new Error('Cannot set static values');
                }

                if (meta.defs[field].hasOwnProperty('processIn') && typeof meta.defs[field].processIn === 'function') {
                    meta.data[field] = meta.defs[field].processIn(value);
                } else {
                    meta.data[field] = value;
                }

                meta.isEmpty = false;
            });
        }.bind(this));

        //extend controllers
        Object.keys(this.controllers).forEach(function (controller) {
            model[controller] = this.controllers[controller];
        }.bind(this));

        //load data en masse into the model
        //works recursively
        model.loadData = function (value) {
            var meta = this.__verymeta;

            if (Array.isArray(meta.defs)) {
                meta.data = [];
                var valueoff = 0;
                for (var vidx in meta.defs) {
                    if (!meta.defs[vidx].required && value.length + valueoff < meta.defs.length) {
                        if (meta.defs[vidx].default === 'function') {
                            this[vidx] = meta.defs[vidx].default(this);
                        } else {
                            this[vidx] = meta.defs[vidx].default;
                        }
                        valueoff += 1;
                    } else {
                        this[vidx] = value[vidx - valueoff];
                    }
                }
            } else {
                Object.keys(value).forEach(function (key) {
                    if (!meta.defs.hasOwnProperty(key)) return;

                    if (meta.defs[key].hasOwnProperty('collection')) {
                        for (var vidx in value[key]) {
                            meta.isEmpty = false;
                            meta.data[key].push(value[key][vidx]);
                        }
                    } else {
                        meta.isEmpty = false;
                        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 || {};

            var meta = this.__verymeta;
            var obj;

            if (Array.isArray(meta.defs) && !opts.useKeywords) {
                obj = [];
            } else {
                obj = {};
            }

            Object.keys(meta.defs).forEach(function (field) {
                var key = field;
                if (meta.defs[field].private && !opts.withPrivate) {
                    return;
                }

                if (opts.useKeywords && meta.reverseMap.hasOwnProperty(field)) {
                    key = meta.reverseMap[field];
                }

                if (meta.defs[field].hasOwnProperty('model')) {
                    if (!meta.data[field].__verymeta.isEmpty) {
                        obj[key] = meta.data[field].toObject();
                    }
                    return;
                }

                if (meta.defs[field].hasOwnProperty('collection')) {
                    obj[key] = [];
                    meta.data[field].forEach(function (inst) {
                        if (!inst.__verymeta.isEmpty) {
                            obj[key].push(inst.toObject());
                        }
                    });
                    if (obj[key].length === 0) {
                        delete obj[key];
                    }

                    return;
                }

                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 meta = this.__verymeta;
            var errors = [];

            Object.keys(meta.defs).forEach(function (field) {
                var fidx;
                var merrors;
                //if field is set and dependency is set
                if (meta.data.hasOwnProperty(field) && meta.defs[field].depends) {
                    for (fidx in meta.defs[field].depends) {
                        //check to see if other field is set
                        if (!meta.data.hasOwnProperty(fidx) || meta.data[fidx] === undefined) {
                            errors.push(field + ": Dependency -> " + fidx + ": not set");
                        //check to see if dependency has a validator
                        } else if (meta.defs[field].depends instanceof VeryValidator) {
                            merrors = meta.defs[field].depends[fidx].validate(meta.data[fidx]);
                            merrors.forEach(function (error) {
                                errors.push(field + ": Dependency  -> " + fidx + ": " + error);
                            });
                        }
                    }
                }
                //if field is reuqired and it's not set
                if (meta.defs[field].required && (!meta.data.hasOwnProperty(field) || meta.data[field] === undefined)) {
                    errors.push(field + ": required");
                }
                //if this field is another model
                //recursively validate it
                //add errors to our own
                //only if the submodule is required or has been populated
                if (meta.defs[field].hasOwnProperty('model') && meta.data.hasOwnProperty(field) && (!meta.data[field].__verymeta.isEmpty || meta.defs[field].required)) {
                    merrors = meta.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 (meta.defs[field].hasOwnProperty('collection') && meta.data.hasOwnProperty(field)) {
                    var arrayerrors = [];
                    var idx = 0;
                    meta.data[field].forEach(function (model) {
                        //is this collection required? has the model been populated?
                        if (meta.defs[field].required || !model.__verymeta.isEmpty) {
                            merrors = model.doValidate();
                            for (var eidx in merrors) {
                                merrors[eidx] = field + '[' + idx + '].' + merrors[eidx];
                            }
                            errors = errors.concat(merrors);
                        }
                        idx += 1;
                    }.bind(this));
                //if we have a VeryValidator to validate against, validate it
                } else if (meta.data.hasOwnProperty(field) && meta.defs[field].hasOwnProperty('type')) {
                    if (typeof meta.defs[field].type === 'string' && Types.hasOwnProperty(meta.defs[field].type)) {
                        meta.defs[field].type = Types[meta.defs[field].type];
                    }
                    merrors = meta.defs[field].type.validate(meta.data[field]);
                    if (typeof merrors === 'boolean') {
                        if (merrors) {
                            merrors = [];
                        } else {
                            merrors = ["Failed."];
                        }
                    }
                    merrors.forEach(function (error) {
                        errors.push(field + ": " + error);
                    });
                } else if (meta.data.hasOwnProperty(field) && meta.defs[field].hasOwnProperty('array')) {
                    var tidx = 0;
                    meta.defs[field].array.forEach(function (type) {
                        merrors = type.validate(meta.data[field][tidx]);
                        merrors.forEach(function (error) {
                            errors.push(field + "[" + tidx + "]: " + error);
                        });
                        tidx++;
                    }.bind(this));
                }

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

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

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

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

        meta.creating = false;

        return model;
    };

    this.extendModel = function (controllers) {
        this.controllers = underscore.extend(this.controllers, controllers);
    };

    this.addModelVar = function (name, value) {
        this.controllers[name] = value;
    };

}).call(VeryModel.prototype);

module.exports = VeryModel;
<|endoftext|>


<commit_before>'use strict'

let fs = require('fs')
let path = require('path')
let _ = require('lodash')
let assert = require('assert-plus')
let mkdirp = require('mkdirp')
let del = require('del')
let uuid = require('node-uuid')

let imponderous = require('./')

class Model {
  constructor (keyOrData) {
    let args = Array.prototype.slice.call(arguments)

    if (_.isString(keyOrData)) {
      this._key = keyOrData
      args.shift()
    }

    this.data = _.extend({}, this.defaults/*, ...args*/)

    // TODO: Use ES6 spread operator
    _.each(args, (obj) => {
      _.extend(this.data, obj)
    })

    // Setup getters/setters
    _.each(this.constructor.schema, (props, field) => {
      Object.defineProperty(this, field, {
        enumerable: true,
        configurable: true,
        get: function () {
          return this.get(field)
        },
        set: function (v) {
          return this.set(field, v)
        }
      })
    })
  }

  // ----- Document methods -----

  // return the document key
  get key () {
    if (_.isUndefined(this._key)) {
      let keyField = _.keys(this.schema).filter(field => {
        return this.schema[field].key
      })

      assert.ok(keyField.length === 0 || keyField.length === 1, 'Schema fields has multiple "key" properties')

      if (keyField.length > 0) {
        keyField = _.first(keyField)

        this._key = this.get(keyField)
      }

      if (_.isUndefined(this._key)) {
        // TODO: Consider using uuid.v4 instead
        this._key = uuid.v1()
      }
    }

    return this._key
  }

  set key (newKey) {
    if (this._key) {
      this._oldKey = this._key
    }

    this._key = newKey
  }

  get (field) {
    return this._magicGetterSetter(this.data, field)
  }

  set (field, value) {
    if (_.isString(field) || _.isArray(field)) {
      return this._magicGetterSetter(this.data, field, value)
    }

    // TODO: Use ES6 spread operator
    _.each(arguments, (obj) => {
      _.extend(this.data, obj)
    })
  }

  _magicGetterSetter (obj, is, value) {
    if (typeof is === 'string') {
      return this._magicGetterSetter(obj, is.split('.'), value)
    } else if (is.length === 1 && value !== undefined) {
      obj[is[0]] = value
      return value
    } else if (is.length === 0) {
      return obj
    } else {
      return this._magicGetterSetter(obj[is[0]], is.slice(1), value)
    }
  }

  rm (field) {
    function reduce (obj, is) {
      if (is.length === 1) {
        try {
          delete obj[is[0]]
        } catch (ex) {
          // this means it didn't exist to begin with
        }
      } else if (is.length === 0) {
        return obj
      } else {
        return reduce(obj[is[0]], is.slice(1))
      }
    }

    // covert all fields requested to be deleted to path ararys
    let fields = _.map(arguments, (field) => {
      if (typeof field === 'string') {
        return field.split('.')
      } else {
        return field
      }
    })

    // do the deleting
    _.each(fields, (field) => {
      reduce(this.data, field)
    })

    return this
  }

  isValid (silent) {
    // ensure all requires fields are present
    let required = this.constructor.required
    assert.arrayOfString(required, 'Model.required')
    let missing = _.filter(required, (field) => {
      return _.isUndefined(this.get(field))
    })

    if (missing.length > 0) {
      if (!silent) {
        throw new Error('Missing required fields of: ' + missing.join(', '))
      }

      return false
    }

    // check values of properties
    // TODO

    return true
  }

  toJSON () {
    return this.data
  }

  toObject () {
    return this.data
  }

  toString () {
    return imponderous.formatter.toString(this.data)
  }

  get indexes () {
    return _.pick(this.data, this.constructor.indexed)
  }

  // return location of this document
  get file () {
    assert.string(this.key)
    assert.string(imponderous.formatter.ext)
    let filename = this.key + imponderous.formatter.ext
    let file = path.join(this.constructor.root, filename)
    return file
  }

  // save document to disk
  save () {
    if (arguments.length > 0) {
      this.set.apply(this, arguments)
    }

    let contents = imponderous.formatter.toString(this.data)

    return new Promise((resolve, reject) => {
      // ensure the colleciton directory exists
      mkdirp(this.constructor.root, (err) => {
        if (err) {
          reject(err)
          return
        }

        // delete old document if necessary
        if (this._oldKey) {
          let oldFile = path.join(this.constructor.root, this._oldKey + imponderous.formatter.ext)
          fs.unlinkSync(oldFile)
          // TODO: Make this use async
        }

        // write the document file
        fs.writeFile(this.file, contents, 'utf8', (err) => {
          if (err) {
            return reject(err)
          }

          console.log(this, this.constructor)

          // update the index
          this.constructor.reindex().then(() => {
            resolve(this)
          }, reject)
        })
      })
    })
  }

  // destroy a document, deleting it from disk
  destroy () {
    return new Promise((resolve, reject) => {
      fs.unlink(this.file, function (err) {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  // load document's data from disk
  _load () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.file, 'utf8', (err, contents) => {
        if (err) {
          reject(err)
        } else {
          try {
            this.data = imponderous.formatter.toObject(contents)
          } catch (ex) {
            reject(ex)
          } finally {
            resolve(this)
          }
        }
      })
    })
  }

  // ----- Model methods -----

  // retrieve a document by its key
  static get (key, load) {
    if (!this.cache) {
      this.cache = {}
    }

    if (typeof this.cache[key] === 'undefined') {
      let doc = new this(key)
      this.cache[key] = doc

      if (load !== false) {
        doc._load()
      }
    }

    return this.cache[key]
  }

  // static destroy (key) {}

  // delete an entire collection
  static destroyAll (collectionName) {
    return del(path.join(this.root, '**'))
  }

  // find documents matching search
  static find (query, options) {
    assert.object(query, 'query')
    assert.ok(_.keys(query).length > 0, 'query')
    options = options || {}

    return new Promise((resolve, reject) => {
      this._loadIndex().catch(reject).then((index) => {
        index = _.clone(index)

        if (_.keys(index).length === 0) {
          return resolve(options.limit === 1 ? null : [])
        }

        let items = {}

        _.each(index, (fields, key) => {
          let matches = _.every(query, (value, field) => {
            return fields[field] === value
          })

          if (matches) {
            items[key] = fields
          }
        })

        if (options.limit === 1) {
          items = items.slice(0, 1)
        }

        let docs = _.map(items, (fields, key) => {
          return this.get(key, false)
        })

        resolve(docs)
      })
    })
  }

  static findOne (params) {
    return this.find(params, { limit: 1 })
  }

  static all (loadAll) {
    return new Promise((resolve, reject) => {
      fs.readdir(this.root, (err, files) => {
        if (err) {
          return reject(err)
        }

        files = _.without(files, 'index.json')

        let docs = {}

        let promises = _.map(files, (filename) => {
          let key = path.basename(filename, imponderous.formatter.ext)
          assert.notEqual(key, filename)
          let doc = new this(key)
          docs[key] = doc
          if (loadAll) {
            return doc._load()
          }
        })

        if (loadAll) {
          Promise.all(promises).then(() => {
            resolve(docs)
          })
        } else {
          resolve(docs)
        }
      })
    })
  }

  // return the pluralized name for the collection
  static get collectionName () {
    return this.name.toLowerCase().trim() + 's'
  }

  // return the directory for this collection
  static get root () {
    return path.join(imponderous.root, this.collectionName)
  }

  // return the absolute path to the index file
  static get indexFile () {
    return path.join(this.root, 'index.json')
  }

  // return the default values for model, based on schema
  static get defaults () {
    return _.mapValues(this.schema, 'default')
  }

  // return the list of fields that are required, based on schema
  static get required () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].required
    })
  }

  // return the list of fields that should be indexed, based on schema
  static get indexed () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].index
    })
  }

  static reindex (useCache) {
    useCache = useCache !== false

    // using the cache assumes that the index file has some content,
    // and that it is still accurate, so we only need to update it
    // with the pending changes we know of
    if (useCache && this.cache) {
      // this.cache
      // TODO
    }

    this.all(true).then((docs) => {
      let data = _.mapValues(docs, 'indexes')
      data = imponderous.formatter.toString(data)

      fs.writeFile(this.indexFile, data, 'utf8', function (err) {
        if (err) {
          console.error(err)
        } else {
          console.log('Write index')
        }
      })
    })
  }

  static _loadIndex () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.indexFile, 'utf8', (err, contents) => {
        if (err) {
          // If file does not exist, then no index has been created,
          // that's not necessarily an error
          if (err.code === 'ENOENT' && err.errno === -2) {
            resolve({})
          }

          reject(err)
          return
        }

        try {
          let data = imponderous.formatter.toObject(contents)
          resolve(data)
        } catch (ex) {
          reject(new Error('Invalid JSON formatting for collection index'))
        }
      })
    })
  }
}

module.exports = Model
<commit_msg>On key reassignment, also update the collection cache.<commit_after>'use strict'

let fs = require('fs')
let path = require('path')
let _ = require('lodash')
let assert = require('assert-plus')
let mkdirp = require('mkdirp')
let del = require('del')
let uuid = require('node-uuid')

let imponderous = require('./')

class Model {
  constructor (keyOrData) {
    let args = Array.prototype.slice.call(arguments)

    if (_.isString(keyOrData)) {
      this._key = keyOrData
      args.shift()
    }

    this.data = _.extend({}, this.defaults/*, ...args*/)

    // TODO: Use ES6 spread operator
    _.each(args, (obj) => {
      _.extend(this.data, obj)
    })

    // Setup getters/setters
    _.each(this.constructor.schema, (props, field) => {
      Object.defineProperty(this, field, {
        enumerable: true,
        configurable: true,
        get: function () {
          return this.get(field)
        },
        set: function (v) {
          return this.set(field, v)
        }
      })
    })
  }

  // ----- Document methods -----

  // return the document key
  get key () {
    if (_.isUndefined(this._key)) {
      let keyField = _.keys(this.schema).filter(field => {
        return this.schema[field].key
      })

      assert.ok(keyField.length === 0 || keyField.length === 1, 'Schema fields has multiple "key" properties')

      if (keyField.length > 0) {
        keyField = _.first(keyField)

        this._key = this.get(keyField)
      }

      if (_.isUndefined(this._key)) {
        // TODO: Consider using uuid.v4 instead
        this._key = uuid.v1()
      }
    }

    return this._key
  }

  // when the key is updated, we need to update the collection cache,
  // and reindex (remove old key) along with removing the old file
  set key (newKey) {
    if (this._key) {
      this._oldKey = this._key
    }

    this._key = newKey

    if (this.constructor.cache[this._oldKey]) {
      this.constructor.cache[this._key] = this
      delete this.constructor.cache[this._oldKey]
    }
  }

  get (field) {
    return this._magicGetterSetter(this.data, field)
  }

  set (field, value) {
    if (_.isString(field) || _.isArray(field)) {
      return this._magicGetterSetter(this.data, field, value)
    }

    // TODO: Use ES6 spread operator
    _.each(arguments, (obj) => {
      _.extend(this.data, obj)
    })
  }

  _magicGetterSetter (obj, is, value) {
    if (typeof is === 'string') {
      return this._magicGetterSetter(obj, is.split('.'), value)
    } else if (is.length === 1 && value !== undefined) {
      obj[is[0]] = value
      return value
    } else if (is.length === 0) {
      return obj
    } else {
      return this._magicGetterSetter(obj[is[0]], is.slice(1), value)
    }
  }

  rm (field) {
    function reduce (obj, is) {
      if (is.length === 1) {
        try {
          delete obj[is[0]]
        } catch (ex) {
          // this means it didn't exist to begin with
        }
      } else if (is.length === 0) {
        return obj
      } else {
        return reduce(obj[is[0]], is.slice(1))
      }
    }

    // covert all fields requested to be deleted to path ararys
    let fields = _.map(arguments, (field) => {
      if (typeof field === 'string') {
        return field.split('.')
      } else {
        return field
      }
    })

    // do the deleting
    _.each(fields, (field) => {
      reduce(this.data, field)
    })

    return this
  }

  isValid (silent) {
    // ensure all requires fields are present
    let required = this.constructor.required
    assert.arrayOfString(required, 'Model.required')
    let missing = _.filter(required, (field) => {
      return _.isUndefined(this.get(field))
    })

    if (missing.length > 0) {
      if (!silent) {
        throw new Error('Missing required fields of: ' + missing.join(', '))
      }

      return false
    }

    // check values of properties
    // TODO

    return true
  }

  toJSON () {
    return this.data
  }

  toObject () {
    return this.data
  }

  toString () {
    return imponderous.formatter.toString(this.data)
  }

  get indexes () {
    return _.pick(this.data, this.constructor.indexed)
  }

  // return location of this document
  get file () {
    assert.string(this.key)
    assert.string(imponderous.formatter.ext)
    let filename = this.key + imponderous.formatter.ext
    let file = path.join(this.constructor.root, filename)
    return file
  }

  // save document to disk
  save () {
    if (arguments.length > 0) {
      this.set.apply(this, arguments)
    }

    let contents = imponderous.formatter.toString(this.data)

    return new Promise((resolve, reject) => {
      // ensure the colleciton directory exists
      mkdirp(this.constructor.root, (err) => {
        if (err) {
          reject(err)
          return
        }

        // delete old document if necessary
        if (this._oldKey) {
          let oldFile = path.join(this.constructor.root, this._oldKey + imponderous.formatter.ext)
          fs.unlinkSync(oldFile)
          // TODO: Make this use async
        }

        // write the document file
        fs.writeFile(this.file, contents, 'utf8', (err) => {
          if (err) {
            return reject(err)
          }

          console.log(this, this.constructor)

          // update the index
          this.constructor.reindex().then(() => {
            resolve(this)
          }, reject)
        })
      })
    })
  }

  // destroy a document, deleting it from disk
  destroy () {
    return new Promise((resolve, reject) => {
      fs.unlink(this.file, function (err) {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  // load document's data from disk
  _load () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.file, 'utf8', (err, contents) => {
        if (err) {
          reject(err)
        } else {
          try {
            this.data = imponderous.formatter.toObject(contents)
          } catch (ex) {
            reject(ex)
          } finally {
            resolve(this)
          }
        }
      })
    })
  }

  // ----- Model methods -----

  // retrieve a document by its key
  static get (key, load) {
    if (!this.cache) {
      this.cache = {}
    }

    if (typeof this.cache[key] === 'undefined') {
      let doc = new this(key)
      this.cache[key] = doc

      if (load !== false) {
        doc._load()
      }
    }

    return this.cache[key]
  }

  // static destroy (key) {}

  // delete an entire collection
  static destroyAll (collectionName) {
    return del(path.join(this.root, '**'))
  }

  // find documents matching search
  static find (query, options) {
    assert.object(query, 'query')
    assert.ok(_.keys(query).length > 0, 'query')
    options = options || {}

    return new Promise((resolve, reject) => {
      this._loadIndex().catch(reject).then((index) => {
        index = _.clone(index)

        if (_.keys(index).length === 0) {
          return resolve(options.limit === 1 ? null : [])
        }

        let items = {}

        _.each(index, (fields, key) => {
          let matches = _.every(query, (value, field) => {
            return fields[field] === value
          })

          if (matches) {
            items[key] = fields
          }
        })

        if (options.limit === 1) {
          items = items.slice(0, 1)
        }

        let docs = _.map(items, (fields, key) => {
          return this.get(key, false)
        })

        resolve(docs)
      })
    })
  }

  static findOne (params) {
    return this.find(params, { limit: 1 })
  }

  static all (loadAll) {
    return new Promise((resolve, reject) => {
      fs.readdir(this.root, (err, files) => {
        if (err) {
          return reject(err)
        }

        files = _.without(files, 'index.json')

        let docs = {}

        let promises = _.map(files, (filename) => {
          let key = path.basename(filename, imponderous.formatter.ext)
          assert.notEqual(key, filename)
          let doc = new this(key)
          docs[key] = doc
          if (loadAll) {
            return doc._load()
          }
        })

        if (loadAll) {
          Promise.all(promises).then(() => {
            resolve(docs)
          })
        } else {
          resolve(docs)
        }
      })
    })
  }

  // return the pluralized name for the collection
  static get collectionName () {
    return this.name.toLowerCase().trim() + 's'
  }

  // return the directory for this collection
  static get root () {
    return path.join(imponderous.root, this.collectionName)
  }

  // return the absolute path to the index file
  static get indexFile () {
    return path.join(this.root, 'index.json')
  }

  // return the default values for model, based on schema
  static get defaults () {
    return _.mapValues(this.schema, 'default')
  }

  // return the list of fields that are required, based on schema
  static get required () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].required
    })
  }

  // return the list of fields that should be indexed, based on schema
  static get indexed () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].index
    })
  }

  static reindex (useCache) {
    useCache = useCache !== false

    // using the cache assumes that the index file has some content,
    // and that it is still accurate, so we only need to update it
    // with the pending changes we know of
    if (useCache && this.cache) {
      // this.cache
      // TODO
    }

    this.all(true).then((docs) => {
      let data = _.mapValues(docs, 'indexes')
      data = imponderous.formatter.toString(data)

      fs.writeFile(this.indexFile, data, 'utf8', function (err) {
        if (err) {
          console.error(err)
        } else {
          console.log('Write index')
        }
      })
    })
  }

  static _loadIndex () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.indexFile, 'utf8', (err, contents) => {
        if (err) {
          // If file does not exist, then no index has been created,
          // that's not necessarily an error
          if (err.code === 'ENOENT' && err.errno === -2) {
            resolve({})
          }

          reject(err)
          return
        }

        try {
          let data = imponderous.formatter.toObject(contents)
          resolve(data)
        } catch (ex) {
          reject(new Error('Invalid JSON formatting for collection index'))
        }
      })
    })
  }
}

module.exports = Model
<|endoftext|>


<commit_before>'use strict';

const debug     = require('debug')('oplog'),
      Timestamp = require('mongodb').Timestamp,
      utils     = require('utils'),
      noop      = utils.noop,
      keys      = Object.keys;

module.exports = require('tofactory')(Oplog);

function Oplog(spec) {
    let dbName, oplogCol, collections, onUpdate, operations, cursor;

    init();

    return {
        get collections() {
            return collections;
        },
        set collections(collectionNames) {
            collections = collectionNames;
            // closes the cursor which triggers the listen function again
            cursor && cursor.close();
        },
        listen
    };

    function init() {
        const db = spec.db.db('local');

        collections = spec.collections || [];
        dbName = spec.db.databaseName;
        oplogCol = db.collection("oplog.rs");

        operations = {
            i: spec.onInsert || noop,
            u: updateOp,
            d: spec.onRemove || noop
        };

        onUpdate = spec.onUpdate || noop;
    }

    function listen(next) {
        // Find the highest timestamp
        oplogCol.find({})
            .project({ts: true})
            .sort({$natural: -1})
            .limit(1)
            .next(function (err, oldestChange) {
                if (err) return next(err);

                // If there isn't one found, get one from the local clock
                if (oldestChange) {
                    oldestChange = oldestChange.ts;
                } else {
                    oldestChange = new Timestamp(0,
                        Math.floor(new Date().getTime() / 1000));
                }

                handleChangesStream(oldestChange, next);
            });
    }

    function handleChangesStream(oldestChange, next) {
        let stream;

        // Create a cursor for tailing and set it to await data
        const query = {
            ts: {$gt: oldestChange},
            ns: {$regex: `^${dbName}.(${collections.join('|')})$`}
        };

        cursor = oplogCol.find(query, {numberOfRetries: Number.MAX_VALUE})
            .addCursorFlag('tailable', true)
            .addCursorFlag('awaitData', true)
            .addCursorFlag('oplogReplay', true);

        // Wrap that cursor in a Node Stream
        stream = cursor.stream();

        stream.on('data', function(doc) {
            try {
                operations[doc.op](doc.ns.split('.')[1], doc.o, doc);
            } catch (e) {
                debug(e);
                debug(doc);
            }
        });

        stream.on('error', function (err) {
            debug('stream error');
            debug(err);
            next(err);
        });

        stream.on('end', function (e) {
            debug('stream ended');
            debug(e);
            listen(next);
        });

        stream.on('close', function (e) {
            debug('stream closed');
            debug(e);
        });
    }

    function updateOp(collection, data, doc) {
        let hasOperators = true,
            _id;

        const operators = keys(data);

        // if data has update operators
        if (operators[0] && operators[0][0] === '$') {
            _id = doc.o2._id;
        } else {
            _id = data._id;
        }

        onUpdate(collection, hasOperators, _id, data);
    }

}
<commit_msg>added emitter<commit_after>'use strict';

const debug     = require('debug')('oplog'),
      Timestamp = require('mongodb').Timestamp,
      utils     = require('utils'),
      noop      = utils.noop,
      keys      = Object.keys,
      Emitter   = require('create-emitter');

module.exports = require('tofactory')(Oplog);

function Oplog(spec) {
    let dbName, oplogCol, collections, onUpdate, operations, cursor;

    const emitter = Emitter();

    init();

    return {
        get collections() {
            return collections;
        },
        set collections(collectionNames) {
            collections = collectionNames;
            // closes the cursor which triggers the listen function again
            cursor && cursor.close();
        },
        listen
    };

    function init() {
        const db = spec.db.db('local');

        collections = spec.collections || [];
        dbName = spec.db.databaseName;
        oplogCol = db.collection("oplog.rs");

        operations = {
            i: spec.onInsert || noop,
            u: updateOp,
            d: spec.onRemove || noop
        };

        onUpdate = spec.onUpdate || noop;
    }

    function listen(done) {
        done = done || noop;
        // Find the highest timestamp
        oplogCol.find({})
            .project({ts: true})
            .sort({$natural: -1})
            .limit(1)
            .next(function (err, oldestChange) {
                if (err) return done(err);

                // If there isn't one found, get one from the local clock
                if (oldestChange) {
                    oldestChange = oldestChange.ts;
                } else {
                    oldestChange = new Timestamp(0,
                        Math.floor(new Date().getTime() / 1000));
                }

                handleChangesStream(oldestChange, done);
            });
    }

    function handleChangesStream(oldestChange, done) {
        let stream;

        // Create a cursor for tailing and set it to await data
        const query = {
            ts: {$gt: oldestChange},
            ns: {$regex: `^${dbName}.(${collections.join('|')})$`}
        };

        cursor = oplogCol.find(query, {numberOfRetries: Number.MAX_VALUE})
            .addCursorFlag('tailable', true)
            .addCursorFlag('awaitData', true)
            .addCursorFlag('oplogReplay', true);

        // Wrap that cursor in a Node Stream
        stream = cursor.stream();

        stream.on('data', function(doc) {
            try {
                operations[doc.op](doc.ns.split('.')[1], doc.o, doc);
            } catch (e) {
                debug(e);
                debug(doc);
            }
        });

        stream.on('error', function (err) {
            debug('stream error');
            debug(err);
            emitter.emit('error', err);
        });

        stream.on('end', function (e) {
            debug('stream ended');
            debug(e);
            handleCursorConn();
        });

        stream.on('close', function (e) {
            debug('stream closed');
            debug(e);
        });

        done();
    }

    function updateOp(collection, data, doc) {
        let hasOperators = true,
            _id;

        const operators = keys(data);

        // if data has update operators
        if (operators[0] && operators[0][0] === '$') {
            _id = doc.o2._id;
        } else {
            _id = data._id;
        }

        onUpdate(collection, hasOperators, _id, data);
    }

    function handleCursorConn() {
        // TODO: put here a control over cursor "retries" to prevent
        // infinite looping, but for now, only listen again on cursor is closed
        listen();
    }
}
<|endoftext|>


<commit_before>'use strict'

const moment = require('moment-timezone')
const util = require('vbb-util')
const shorten = require('vbb-short-station-name')
const line = require('vbb-parse-line')
const omit = require('lodash.omit')

const dateTime = (date, time) => {
	let offset = 0 // in days
	if (time.length > 6) {
		offset = +time.slice(0, -6)
		time = time.slice(-6)
	}
	return moment.tz(date + 'T' + time, 'Europe/Berlin')
	.add(offset, 'days')
	.valueOf()
}

const types = {P: 'poi', S: 'station', A: 'address'}
// todo: what is s.rRefL?
const location = (l) => {
	const type = types[l.type] || 'unknown'
	const result = {type
		, latitude:  l.crd ? l.crd.y / 1000000 : null
		, longitude: l.crd ? l.crd.x / 1000000 : null
		, name:      shorten(l.name)
	}
	if (type === 'poi' || type === 'station') result.id = parseInt(l.extId)
	if ('pCls' in l) result.products = util.products.parseBitmask(l.pCls)
	return result
}

// todo: what is p.number vs p.line?
// todo: what is p.icoX?
// todo: what is p.cls?
// todo: what is p.oprX?
const product = (p) => {
	if (!p.prodCtx) return null
	const result = {
		  line: p.prodCtx.line
		, type: util.products.categories[parseInt(p.prodCtx.catCode)]
	}
	Object.assign(result, omit(line(p.prodCtx.line), ['type', '_']))
	return result
}

const remark = (r) => null // todo: vbb-parse-remarks

// s = stations, p = products, r = remarks, c = connection
const stop = (s, p, r, c) => (st) => {
	const result = {station:   s[parseInt(st.locX)]}
	if (st.aTimeR || st.aTimeS) result.arrival =
		new Date(dateTime(c.date, st.aTimeR || st.aTimeS))
	if (st.dTimeR || st.dTimeS) result.departure =
		new Date(dateTime(c.date, st.dTimeR || st.dTimeS))
	return result
}

// todo: finish parseRemark first
// s = stations, p = products, r = remarks, c = connection
const applyRemark = (s, p, r, c) => (rm) => null

// todo: pt.sDays
// todo: pt.dep.dProgType, pt.arr.dProgType
// todo: what is pt.jny.dirFlg?
// todo: how does pt.freq work?
// s = stations, p = products, r = remarks, c = connection
const part = (s, p, r, c) => (pt) => {
	const result = {
		  from:      s[parseInt(pt.dep.locX)]
		, to:        s[parseInt(pt.arr.locX)]
		, start:     new Date(dateTime(c.date, pt.dep.dTimeR || pt.dep.dTimeS))
		, end:       new Date(dateTime(c.date, pt.dep.aTimeR || pt.arr.aTimeS))
	}
	if (pt.type === 'WALK') result.type = 'walking'
	else if (pt.type === 'JNY') {
		result.product = p[parseInt(pt.jny.prodX)]
		result.direction = pt.jny.dirTxt // todo: parse this
		if (pt.jny.stopL) result.passed = pt.jny.stopL.map(stop(s, p, r, c))
		pt.jny.remL.forEach(applyRemark(s, p, r, c))

		if (pt.jny.freq && pt.jny.freq.jnyL)
			result.alternatives = pt.jny.freq.jnyL
				.filter((a) => a.stopL[0].locX === pt.dep.locX)
				.map((a) => ({
					product: p[parseInt(a.prodX)],
					when:    new Date(dateTime(c.date, a.stopL[0].dTimeS))
				}))
	}
	return result
}

// todo: c.sDays
// todo: c.dep.dProgType, c.arr.dProgType
// todo: c.conSubscr
// todo: c.trfRes x vbb-parse-ticket
// todo: use computed information from part
// s = stations, p = products, r = remarks
const route = (s, p, r) => (c) => {
	const parts = c.secL.map(part(s, p, r, c))
	return {
		  parts
		, from:  parts[0].from
		, start: parts[0].start
		, to:    parts[parts.length - 1].to
		, end:   parts[parts.length - 1].end
	}
}

// todo: what is d.jny.dirFlg?
// todo: d.stbStop.dProgType
// todo: what is d.stbStop.dTimeR?
// s = stations, p = products, r = remarks
const departure = (s, p, r) => (d) => {
	const result = {
		  station:   s[parseInt(d.stbStop.locX)]
		, when:      new Date(dateTime(d.date, d.stbStop.dTimeR || d.stbStop.dTimeS))
		, direction: shorten(d.dirTxt) // todo: parse this
		, product:   p[parseInt(d.prodX)]
		, remarks:   d.remL ? d.remL.map((rm) => r[parseInt(rm.remX)]) : null
	}
	return result
}

// todo: remarks
// todo: products
// todo: what is s.pCls?
// todo: what is s.wt?
// todo: what is s.dur?
const nearby = (n) => {
	const result = location(n)
	result.distance = n.dist
	return result
}

module.exports = {
	dateTime,
	location, product, remark,
	stop, applyRemark, part, route,
	departure,
	nearby
}
<commit_msg>expose delay<commit_after>'use strict'

const moment = require('moment-timezone')
const util = require('vbb-util')
const shorten = require('vbb-short-station-name')
const line = require('vbb-parse-line')
const omit = require('lodash.omit')

const dateTime = (date, time) => {
	let offset = 0 // in days
	if (time.length > 6) {
		offset = +time.slice(0, -6)
		time = time.slice(-6)
	}
	return moment.tz(date + 'T' + time, 'Europe/Berlin')
	.add(offset, 'days')
	.valueOf()
}

const types = {P: 'poi', S: 'station', A: 'address'}
// todo: what is s.rRefL?
const location = (l) => {
	const type = types[l.type] || 'unknown'
	const result = {type
		, latitude:  l.crd ? l.crd.y / 1000000 : null
		, longitude: l.crd ? l.crd.x / 1000000 : null
		, name:      shorten(l.name)
	}
	if (type === 'poi' || type === 'station') result.id = parseInt(l.extId)
	if ('pCls' in l) result.products = util.products.parseBitmask(l.pCls)
	return result
}

// todo: what is p.number vs p.line?
// todo: what is p.icoX?
// todo: what is p.cls?
// todo: what is p.oprX?
const product = (p) => {
	if (!p.prodCtx) return null
	const result = {
		  line: p.prodCtx.line
		, type: util.products.categories[parseInt(p.prodCtx.catCode)]
	}
	Object.assign(result, omit(line(p.prodCtx.line), ['type', '_']))
	return result
}

const remark = (r) => null // todo: vbb-parse-remarks

// s = stations, p = products, r = remarks, c = connection
const stop = (s, p, r, c) => (st) => {
	const result = {station:   s[parseInt(st.locX)]}
	if (st.aTimeR || st.aTimeS) result.arrival =
		new Date(dateTime(c.date, st.aTimeR || st.aTimeS))
	if (st.dTimeR || st.dTimeS) result.departure =
		new Date(dateTime(c.date, st.dTimeR || st.dTimeS))
	return result
}

// todo: finish parseRemark first
// s = stations, p = products, r = remarks, c = connection
const applyRemark = (s, p, r, c) => (rm) => null

// todo: pt.sDays
// todo: pt.dep.dProgType, pt.arr.dProgType
// todo: what is pt.jny.dirFlg?
// todo: how does pt.freq work?
// s = stations, p = products, r = remarks, c = connection
const part = (s, p, r, c) => (pt) => {
	const result = {
		  from:      s[parseInt(pt.dep.locX)]
		, to:        s[parseInt(pt.arr.locX)]
		, start:     new Date(dateTime(c.date, pt.dep.dTimeR || pt.dep.dTimeS))
		, end:       new Date(dateTime(c.date, pt.dep.aTimeR || pt.arr.aTimeS))
	}
	if (pt.type === 'WALK') result.type = 'walking'
	else if (pt.type === 'JNY') {
		result.product = p[parseInt(pt.jny.prodX)]
		result.direction = pt.jny.dirTxt // todo: parse this
		if (pt.jny.stopL) result.passed = pt.jny.stopL.map(stop(s, p, r, c))
		pt.jny.remL.forEach(applyRemark(s, p, r, c))

		if (pt.jny.freq && pt.jny.freq.jnyL)
			result.alternatives = pt.jny.freq.jnyL
				.filter((a) => a.stopL[0].locX === pt.dep.locX)
				.map((a) => ({
					product: p[parseInt(a.prodX)],
					when:    new Date(dateTime(c.date, a.stopL[0].dTimeS))
				}))
	}
	return result
}

// todo: c.sDays
// todo: c.dep.dProgType, c.arr.dProgType
// todo: c.conSubscr
// todo: c.trfRes x vbb-parse-ticket
// todo: use computed information from part
// s = stations, p = products, r = remarks
const route = (s, p, r) => (c) => {
	const parts = c.secL.map(part(s, p, r, c))
	return {
		  parts
		, from:  parts[0].from
		, start: parts[0].start
		, to:    parts[parts.length - 1].to
		, end:   parts[parts.length - 1].end
	}
}

// todo: what is d.jny.dirFlg?
// todo: d.stbStop.dProgType
// todo: what is d.stbStop.dTimeR?
// s = stations, p = products, r = remarks
const departure = (s, p, r) => (d) => {
	const result = {
		  station:   s[parseInt(d.stbStop.locX)]
		, when:      new Date(dateTime(d.date, d.stbStop.dTimeR || d.stbStop.dTimeS))
		, direction: shorten(d.dirTxt) // todo: parse this
		, product:   p[parseInt(d.prodX)]
		, remarks:   d.remL ? d.remL.map((rm) => r[parseInt(rm.remX)]) : null
	}
	if (d.stbStop.dTimeR && d.stbStop.dTimeS) result.delay =
		dateTime(d.date, d.stbStop.dTimeR) - dateTime(d.date, d.stbStop.dTimeS)
	return result
}

// todo: remarks
// todo: products
// todo: what is s.pCls?
// todo: what is s.wt?
// todo: what is s.dur?
const nearby = (n) => {
	const result = location(n)
	result.distance = n.dist
	return result
}

module.exports = {
	dateTime,
	location, product, remark,
	stop, applyRemark, part, route,
	departure,
	nearby
}
<|endoftext|>


<commit_before>/*
 * plato
 * https://github.com/es-analysis/plato
 *
 * Copyright (c) 2012 Jarrod Overson
 * Licensed under the MIT license.
 */

'use strict';

// node api
var path = require('path');

// node api with sugar
var fs = require('fs-extra');

// vendor
var _ = require('lodash');
var glob = require('glob');

// local lib
var util = require('./util'),
    OverviewHistory = require('./models/OverviewHistory'),
    FileHistory = require('./models/FileHistory'),
    Logger = require('./logger'),
    reporters = {
      complexity : require('./reporters/complexity'),
      jshint : require('./reporters/jshint')
    };

var overviewTemplate = __dirname + '/templates/overview.html',
    displayTemplate = __dirname + '/templates/display.html',
    fileTemplate = __dirname + '/templates/file.html',
    assets = __dirname + '/assets/',
    fileDir = 'files';

var log = new Logger(Logger.WARNING);

exports.inspect = function(files, outputDir, options, done) {
	if (!(files && outputDir)) {
		// files and output directory should be defined to save time before doing anything else...
		return;
	}

	if (typeof files === 'string') {
		// it's a file path glob, so let's use glob on it to get an array
		files = glob.sync(files);
	}

  Object.keys(flags).forEach(function(flag){
    if (flag in options) flags[flag] = _.clone(options[flag]);
  });

  if (options.q) log.level = Logger.ERROR;

  if (options.date) {
    // if we think we were given seconds
    if (options.date < 10000000000 ) options.date = options.date * 1000;
    options.date = new Date(options.date);
  }

  var reports = [];

  var fileOutputDir = outputDir ? path.join(outputDir, fileDir) : false;

  var commonBasePath = util.findCommonBase(files);

  var runReports = function(files, done) {
    files.forEach(function(file) {
      if (options.exclude && options.exclude.test(file)) return;

      if (options.recurse && fs.statSync(file).isDirectory()) {
        files = fs.readdirSync(file).map(function(innerFile) {
          return path.join(file,innerFile);
        });
        runReports(files);
      } else if (file.match(/\.js$/)) {
        log.info('Reading "%s"', file);

        var fileShort = file.replace(commonBasePath, '');
        var fileSafe = fileShort.replace(/[^a-zA-Z0-9]/g,'_');
        var source = fs.readFileSync(file).toString().trim();
        if (!source) {
          log.info('Not parsing empty file "%s"', file);
          return;
        }

        // if skip empty line option
        if(options.noempty) {
          source = source.replace(/^\s*[\r\n]/gm ,"");
        }

        // if begins with shebang
        if (source[0] === '#' && source[1] === '!') {
          source = '//' + source;
        }
        var report = {
          info : {
            file : file,
            fileShort : fileShort,
            fileSafe : fileSafe,
            link : fileDir + '/' + fileSafe + '/index.html'
          }
        };

        var error = false;
        _.each(reporters,function(reporter, name) {
          if (!flags[name]) return;
          try {
            report[name] = reporter.process(source, flags[name], report.info);
          } catch (e) {
            error = true;
            log.error('Error reading file %s: ', file, e.toString());
            log.error(e.stack);
          }
        });

        if (error) return;
        reports.push(report);
        if (fileOutputDir) {
          var outdir = path.join(fileOutputDir, report.info.fileSafe);
          if (!fs.existsSync(outdir)) fs.mkdirSync(outdir);
          writeFileReport(outdir, report, source, options);
        }
      }
    });
    if (done) done();
  };

  if (!fileOutputDir) {
    runReports(files,function() {
      done(reports);
    });
  } else {
    fs.mkdirp(fileOutputDir,function() {
      runReports(files,function() {
        var reportFilePrefix = path.join(outputDir, 'report');
        var overview = path.join(outputDir, 'index.html');
        var wallDisplay = path.join(outputDir, 'display.html');

        fs.copy(assets, path.join(outputDir, 'assets'), function() {
          var overviewReport = exports.getOverviewReport(reports);
          updateHistoricalOverview(reportFilePrefix, overviewReport, options);
          writeReport(reportFilePrefix, overviewReport);
          writeOverview(overview, overviewReport, {
            title : options.title,
            flags : flags
          }, overviewTemplate);
          writeOverview(wallDisplay, overviewReport, {
            title : options.title,
            flags : flags
          }, displayTemplate);
          done(reports);
        });
      });
    });
  }
};

// Filters out information unused in the overview for space/performance
exports.getOverviewReport = function (reports) {
  var culledReports = [];

  var summary = {
    total : {
      sloc : 0,
      maintainability : 0,
    },
    average : {
      sloc : 0,
      maintainability : 0,
    }
  };

  reports.forEach(function(report) {
    // clone objects so we don't have to worry about side effects
    summary.total.sloc += report.complexity.aggregate.complexity.sloc.physical;
    summary.total.maintainability += report.complexity.maintainability;

    var aggregate = _.cloneDeep(report.complexity.aggregate);
    var reportItem = {};
    reportItem.info = report.info;
    if (report.jshint) {
      reportItem.jshint = {
        messages : report.jshint.messages.length
      };
    }
    if (report.complexity) {
      reportItem.complexity = {
        aggregate : aggregate,
        module : report.complexity.module,
        module_safe : report.complexity.module_safe,
        maintainability : _.cloneDeep(report.complexity.maintainability)
      };
    }
    culledReports.push(reportItem);
  });

  summary.average.sloc = ~~(summary.total.sloc / reports.length);
  summary.average.maintainability = (summary.total.maintainability / reports.length).toFixed(2);

  return {
    summary : summary,
    reports : culledReports
  };
};

function updateHistoricalOverview(outfilePrefix, overview, options) {
  var existingData = util.readJSON(outfilePrefix + '.history.json', options) || {};
  var history = new OverviewHistory(existingData);
  history.addReport(overview, options.date);
  writeReport(outfilePrefix + '.history', history.toJSON(), '__history');
}

function updateHistoricalReport(outfilePrefix, overview, options) {
  var existingData = util.readJSON(outfilePrefix + '.history.json', options) || {};
  var history = new FileHistory(existingData);
  overview.date = options.date;
  history.addReport(overview, options.date);
  writeReport(outfilePrefix + '.history', history.toJSON(), '__history');
}

function writeFile(file, source) {
  log.info('Writing file "%s".', file);
  fs.writeFileSync(file, source, 'utf8');
}

function writeReport(outfilePrefix, report, exportName) {
  var formatted = util.formatJSON(report);

  writeFile(outfilePrefix + '.json', formatted);

  exportName = exportName || '__report';

  var module = exportName + ' = ' + formatted;

  writeFile(outfilePrefix + '.js', module);
}

function writeOverview(outfile, report, options, templatePath) {
  var overviewSource = fs.readFileSync(templatePath).toString();
  var parsed = _.template(overviewSource, {
    report : report,
    options : options
  });
  writeFile(outfile, parsed);
}

function writeFileReport(outdir, report, source, options) {
  var overviewSource = fs.readFileSync(fileTemplate).toString();
  var parsed = _.template(overviewSource, {
    source : util.escapeHTML(source),
    report : report
  });
  var indexPath = path.join(outdir,'index.html');
  var outfilePrefix = path.join(outdir,'report');

  writeFile(indexPath, parsed);
  updateHistoricalReport(outfilePrefix, report, options);
  writeReport(outfilePrefix, report);
}

<commit_msg>Update plato.js<commit_after>/*
 * plato
 * https://github.com/es-analysis/plato
 *
 * Copyright (c) 2012 Jarrod Overson
 * Licensed under the MIT license.
 */

'use strict';

// node api
var path = require('path');

// node api with sugar
var fs = require('fs-extra');

// vendor
var _ = require('lodash');
var glob = require('glob');

// local lib
var util = require('./util'),
    OverviewHistory = require('./models/OverviewHistory'),
    FileHistory = require('./models/FileHistory'),
    Logger = require('./logger'),
    reporters = {
      complexity : require('./reporters/complexity'),
      jshint : require('./reporters/jshint')
    };

var overviewTemplate = __dirname + '/templates/overview.html',
    displayTemplate = __dirname + '/templates/display.html',
    fileTemplate = __dirname + '/templates/file.html',
    assets = __dirname + '/assets/',
    fileDir = 'files';

var log = new Logger(Logger.WARNING);

exports.inspect = function(files, outputDir, options, done) {
	if (!(files && outputDir)) {
		// files and output directory should be defined to save time before doing anything else...
		return;
	}

	if (typeof files === 'string') {
		// it's a file path glob, so let's use glob on it to get an array
		files = glob.sync(files);
	}

	var flags = {
    complexity : {
      logicalor : true,
      switchcase : true,
      forin : false,
      trycatch : false,
      newmi : true
    },
    jshint : {
      options : {},  // use jshint defaults
      globals : {}
    }
  };

  Object.keys(flags).forEach(function(flag){
    if (flag in options) flags[flag] = _.clone(options[flag]);
  });

  if (options.q) log.level = Logger.ERROR;

  if (options.date) {
    // if we think we were given seconds
    if (options.date < 10000000000 ) options.date = options.date * 1000;
    options.date = new Date(options.date);
  }

  var reports = [];

  var fileOutputDir = outputDir ? path.join(outputDir, fileDir) : false;

  var commonBasePath = util.findCommonBase(files);

  var runReports = function(files, done) {
    files.forEach(function(file) {
      if (options.exclude && options.exclude.test(file)) return;

      if (options.recurse && fs.statSync(file).isDirectory()) {
        files = fs.readdirSync(file).map(function(innerFile) {
          return path.join(file,innerFile);
        });
        runReports(files);
      } else if (file.match(/\.js$/)) {
        log.info('Reading "%s"', file);

        var fileShort = file.replace(commonBasePath, '');
        var fileSafe = fileShort.replace(/[^a-zA-Z0-9]/g,'_');
        var source = fs.readFileSync(file).toString().trim();
        if (!source) {
          log.info('Not parsing empty file "%s"', file);
          return;
        }

        // if skip empty line option
        if(options.noempty) {
          source = source.replace(/^\s*[\r\n]/gm ,"");
        }

        // if begins with shebang
        if (source[0] === '#' && source[1] === '!') {
          source = '//' + source;
        }
        var report = {
          info : {
            file : file,
            fileShort : fileShort,
            fileSafe : fileSafe,
            link : fileDir + '/' + fileSafe + '/index.html'
          }
        };

        var error = false;
        _.each(reporters,function(reporter, name) {
          if (!flags[name]) return;
          try {
            report[name] = reporter.process(source, flags[name], report.info);
          } catch (e) {
            error = true;
            log.error('Error reading file %s: ', file, e.toString());
            log.error(e.stack);
          }
        });

        if (error) return;
        reports.push(report);
        if (fileOutputDir) {
          var outdir = path.join(fileOutputDir, report.info.fileSafe);
          if (!fs.existsSync(outdir)) fs.mkdirSync(outdir);
          writeFileReport(outdir, report, source, options);
        }
      }
    });
    if (done) done();
  };

  if (!fileOutputDir) {
    runReports(files,function() {
      done(reports);
    });
  } else {
    fs.mkdirp(fileOutputDir,function() {
      runReports(files,function() {
        var reportFilePrefix = path.join(outputDir, 'report');
        var overview = path.join(outputDir, 'index.html');
        var wallDisplay = path.join(outputDir, 'display.html');

        fs.copy(assets, path.join(outputDir, 'assets'), function() {
          var overviewReport = exports.getOverviewReport(reports);
          updateHistoricalOverview(reportFilePrefix, overviewReport, options);
          writeReport(reportFilePrefix, overviewReport);
          writeOverview(overview, overviewReport, {
            title : options.title,
            flags : flags
          }, overviewTemplate);
          writeOverview(wallDisplay, overviewReport, {
            title : options.title,
            flags : flags
          }, displayTemplate);
          done(reports);
        });
      });
    });
  }
};

// Filters out information unused in the overview for space/performance
exports.getOverviewReport = function (reports) {
  var culledReports = [];

  var summary = {
    total : {
      sloc : 0,
      maintainability : 0,
    },
    average : {
      sloc : 0,
      maintainability : 0,
    }
  };

  reports.forEach(function(report) {
    // clone objects so we don't have to worry about side effects
    summary.total.sloc += report.complexity.aggregate.complexity.sloc.physical;
    summary.total.maintainability += report.complexity.maintainability;

    var aggregate = _.cloneDeep(report.complexity.aggregate);
    var reportItem = {};
    reportItem.info = report.info;
    if (report.jshint) {
      reportItem.jshint = {
        messages : report.jshint.messages.length
      };
    }
    if (report.complexity) {
      reportItem.complexity = {
        aggregate : aggregate,
        module : report.complexity.module,
        module_safe : report.complexity.module_safe,
        maintainability : _.cloneDeep(report.complexity.maintainability)
      };
    }
    culledReports.push(reportItem);
  });

  summary.average.sloc = ~~(summary.total.sloc / reports.length);
  summary.average.maintainability = (summary.total.maintainability / reports.length).toFixed(2);

  return {
    summary : summary,
    reports : culledReports
  };
};

function updateHistoricalOverview(outfilePrefix, overview, options) {
  var existingData = util.readJSON(outfilePrefix + '.history.json', options) || {};
  var history = new OverviewHistory(existingData);
  history.addReport(overview, options.date);
  writeReport(outfilePrefix + '.history', history.toJSON(), '__history');
}

function updateHistoricalReport(outfilePrefix, overview, options) {
  var existingData = util.readJSON(outfilePrefix + '.history.json', options) || {};
  var history = new FileHistory(existingData);
  overview.date = options.date;
  history.addReport(overview, options.date);
  writeReport(outfilePrefix + '.history', history.toJSON(), '__history');
}

function writeFile(file, source) {
  log.info('Writing file "%s".', file);
  fs.writeFileSync(file, source, 'utf8');
}

function writeReport(outfilePrefix, report, exportName) {
  var formatted = util.formatJSON(report);

  writeFile(outfilePrefix + '.json', formatted);

  exportName = exportName || '__report';

  var module = exportName + ' = ' + formatted;

  writeFile(outfilePrefix + '.js', module);
}

function writeOverview(outfile, report, options, templatePath) {
  var overviewSource = fs.readFileSync(templatePath).toString();
  var parsed = _.template(overviewSource, {
    report : report,
    options : options
  });
  writeFile(outfile, parsed);
}

function writeFileReport(outdir, report, source, options) {
  var overviewSource = fs.readFileSync(fileTemplate).toString();
  var parsed = _.template(overviewSource, {
    source : util.escapeHTML(source),
    report : report
  });
  var indexPath = path.join(outdir,'index.html');
  var outfilePrefix = path.join(outdir,'report');

  writeFile(indexPath, parsed);
  updateHistoricalReport(outfilePrefix, report, options);
  writeReport(outfilePrefix, report);
}

<|endoftext|>


<commit_before>
define( [ 'jclass', 'jquery' ], function( Class, $ ) {
	'use strict';

	/*
	===============================================================================
	Class: Model
	===============================================================================
	*/

	var Model = Class._extend({

		/**
		 * Method: init
		 * @parma {Object} aParent
		 */

		init: function( aParent ) {
			this._parent = aParent;
			this._data = { };
		},

		/**
		 * Method: getParent
		 */

		getParent: function( ) {
			return this._parent;
		},

		/**
		 * Method: get
		 * @param {String} aKey
		 */

		get: function( aKey ) {
			var key = aKey || false,
				val = this._data,
				keys, i, len;

			if ( key !== false ) {
				keys = key.split( '.' );

				for ( i = 0, len = keys.length; i < len; i++ ) {
					if ( val.hasOwnProperty( keys[ i ] ) === true ) {
						val = val[ keys[ i ] ];
					}
					else {
						val = false;
						break;
					}
				}
			}

			return val;
		},

		/**
		 * Method: set
		 * @param {String} aKey
		 * @param {Mixed} aValue
		 */

		set: function( aKey, aValue ) {
			var keys = aKey.split( '.' ),
				arr = this._data,
				i, len;

			for ( i = 0, len = keys.length; i < len; i++ ) {
				if ( arr.hasOwnProperty( keys[ i ] ) === true ) {
					if ( i === len - 1 ) {
						if ( arr[ keys[ i ] ].constructor === Array && aValue.constructor === Array ) {
							arr[ keys[ i ] ] = $.merge( arr[ keys[ i ] ], aValue );
						}
						else {
							arr[ keys[ i ] ] = aValue;
						}
					}
					else {
						arr = arr[ keys[ i ] ];
					}
				}
				else if ( i === len - 1 ) {
					arr[ keys[ i ] ] = aValue;
				}
				else {
					arr[ keys[ i ] ] = [ ];

					arr = arr[ keys[ i ] ];
				}
			}
		},

		/**
		 * Method: remove
		 * @param {Object} aKey
		 */

		remove: function( aKey ) {
			var key = aKey || false,
				val = this._data,
				keys, i, len;

			if ( key !== false ) {
				keys = key.split( '.' );

				for ( i = 0, len = keys.length; i < len; i++ ) {
					if ( val.hasOwnProperty( keys[ i ] ) === true ) {
						if ( i === len - 1 ) {
							delete val[ keys[ i ] ];
						}
						else {
							val = val[ keys[ i ] ];
						}
					}
					else {
						val = false;
						break;
					}
				}
			}
		}
	});

	return Model;
});
<commit_msg>bugfix<commit_after>
define( [ 'jclass', 'jquery' ], function( Class, $ ) {
	'use strict';

	/*
	===============================================================================
	Class: Model
	===============================================================================
	*/

	var Model = Class._extend({

		/**
		 * Method: init
		 * @parma {Object} aParent
		 */

		init: function( aParent ) {
			this._parent = aParent;
			this._data = { };
		},

		/**
		 * Method: getParent
		 */

		getParent: function( ) {
			return this._parent;
		},

		/**
		 * Method: merge
		 * @param {Mixed} aObject
		 * @param {Mixed} bObject
		 */

		merge: function( aObject, bObject ) {
			if ( $.isPlainObject( aObject ) === true && $.isPlainObject( bObject ) === true ) {
				aObject = $.extend( true, aObject, bObject );
			}
			else if ( $.isArray( aObject ) === true && $.isArray( bObject ) === true ) {
				aObject = $.merge( aObject, bObject );
			}
			else {
				aObject = bObject;
			}

			return aObject;
		},

		/**
		 * Method: get
		 * @param {String} aKey
		 */

		get: function( aKey ) {
			var key = aKey || false,
				val = this._data,
				keys, i, len;

			if ( key !== false ) {
				keys = key.split( '.' );

				for ( i = 0, len = keys.length; i < len; i++ ) {
					if ( val.hasOwnProperty( keys[ i ] ) === true ) {
						val = val[ keys[ i ] ];
					}
					else {
						val = false;
						break;
					}
				}
			}

			return val;
		},

		/**
		 * Method: set
		 * @param {String} aKey
		 * @param {Mixed} aValue
		 */

		set: function( aKey, aValue ) {
			var keys = aKey.split( '.' ),
				arr = this._data,
				i, len;

			for ( i = 0, len = keys.length; i < len; i++ ) {
				if ( arr.hasOwnProperty( keys[ i ] ) === true ) {
					if ( i === len - 1 ) {
						arr[ keys[ i ] ] = this.merge( arr[ keys[ i ] ], aValue );
					}
					else {
						arr = arr[ keys[ i ] ];
					}
				}
				else if ( i === len - 1 ) {
					arr[ keys[ i ] ] = this.merge( arr[ keys[ i ] ], aValue );
				}
				else {
					arr[ keys[ i ] ] = [ ];

					arr = arr[ keys[ i ] ];
				}
			}
		},

		/**
		 * Method: remove
		 * @param {Object} aKey
		 */

		remove: function( aKey ) {
			var key = aKey || false,
				val = this._data,
				keys, i, len;

			if ( key !== false ) {
				keys = key.split( '.' );

				for ( i = 0, len = keys.length; i < len; i++ ) {
					if ( val.hasOwnProperty( keys[ i ] ) === true ) {
						if ( i === len - 1 ) {
							delete val[ keys[ i ] ];
						}
						else {
							val = val[ keys[ i ] ];
						}
					}
					else {
						val = false;
						break;
					}
				}
			}
		}
	});

	return Model;
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2014 Scott Beck, all rights reserved
 *
 * Licensed under the MIT license
 *
 */
(function () {
  'use strict';
  var _ = require("lodash"),
    ReSpec = require('./respec.js')(Re);
  Re.registered = {
    hspace:          /[ \t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]/,
    noHspace:        /[^ \t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]/,
    vspace:          /[\v\n\r\f]/,
    number:          /0b[01]+|0o[0-7]+|0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?/,
    noVspace:        /[^\v\n\r\f]/,
    space:           /\s/,
    noSpace:         /\S/,
    word:            /\w/,
    noWord:          /\W/,
    eol:             /(?:\r\n?|\n|\f)/,
    notEol:          /[^\r\n\f]/,
    end:             /$/,
    begin:           /^/,
    cMultiComment:   /\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//
  };
  /** Represents a Regular Matching Spec.
   * Makes it possible to join multiple regular expressions
   * together to form a single match.
   *
   *  var re = new
   *
   *  var re = new Re('foo', /foo/, /bar/)
   *  var re = new Re('foo', {foo: /foo/, bar: /bar/});
   *  var re = new Re('foo', [/foo/, /bar/]);
   *
   *  var re = new Re({foo: /foo/, bar: /bar/});
   *  var re = new Re([/foo/, /bar/]);
   *  var re = new Re([/foo/, /bar/]);
   */
  function genguid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
  }
  function Re(namespace) {
    this.opt = _.clone(Re.defaultOptions);
    var args = _.toArray(arguments);
    this._id = genguid();

    if (_.isString(namespace) || _.isNull(namespace) || _.isUndefined(namespace))
      args = args.slice(1);
    else
      namespace = undefined;

    this._spec = new Re.Spec(namespace, args);
    this._regexn = null;
    this.lastMatch = 0;
  }
  Re.Spec = ReSpec;
  Re.defaultOptions = {
    nsDelimiter: '.'
  };
  Re.register = function (identifier) {
    if (!_.isString(identifier)) {
      _.forEach(identifier, function(val, key) {
          if (!_.isString(key))
            throw new Error("invalid identifier for registerMatch '" + identifier + "'");
          Re.register(key, val);
      });
      return Re;
    }
    if (_.has(Re.registered, identifier))
      throw new Error(identifier + " already in use");

    Re.registered[identifier] = Re.render.apply(Re, _.toArray(arguments).slice(1));

    return Re;
  };
  Re.getRegistered = function (identifier) {
    return Re.registered[identifier];
  };
  Re.canJoin = function (re1, re2) {
    return re1.ignoreCase === re2.ignoreCase && re1.multiline === re2.multiline;
  };
  Re.join = function (regexn) {
    var src = regexn.map(function (re) {
      return re.source;
    }).join(')|(');
    src = '(' + src + ')';
    var flags = '';
    if (regexn[0].ignoreCase) flags += 'i';
    if (regexn[0].multiline) flags += 'm';
    flags += 'g';
    return new RegExp(src, flags);
  };
  Re.prototype.options = function (opt) {
    if (opt) _.merge(this._opt, opt);
    return this._opt;
  };
  Re.prototype.namespace = function (namespace) {
    if (namespace) {
      this._namespace = namespace;
      if (this._regexn)
        this.compile(true);
    }
    return this._namespace;
  };
  Re.prototype.compile = function (force) {
    if (!force && this._regexn) return this._regexn;
    this._regexn = this._spec.compile();
    return this;
  };
  Re.prototype.add = function () {
    this._spec.add(_.toArray(arguments));
    return this;
  };
  Re.prototype.addNamed = function (name) {
    var args = _.toArray(arguments).slice(1);
    this._spec.add({name: name, re: args});
    return this;
  };
  Re.prototype.hasSpecs = function () {
    return this._spec.hasSpecs();
  };
  Re.prototype.exec = function (string) {
    this.compile();
    var regexn = this._regexn,
      startRe = string[this._id] || 0,
      found = false,
      that = this;
    _.forEach(regexn.slice(startRe), function(re, i) {
      var match = re.exec(string);
      if (match) {
        _.forEach(that._spec.resolveMatch(match, i), function (res) {
          if (_.some(res[0], _.isString)) {
            found = res.concat(re.lastIndex);
            return false;
          }
        });
        if (found) {
          string[that._id] = i;
          return false;
        }
      }
    });

    if (!found)
      delete string[this._id];
    return found;
  };
  Re.prototype.test = function (string) {
    this.compile();
    var regexn = this._regexn,
      startRe = this.lastMatch,
      match = false, that = this;
    _.forEach(regexn.slice(startRe), function(re, i) {
       match = re.test(string);
       if (match) that.lastMatch = i;
       return !match;
    });
    return match;
  };
  Re.prototype.reject = function (string) {
    if (!this._regexn)
      throw new Error('reject on non-compiled regexp');
    var lastRe = this._regexn[string[this._id] || 0];
    lastRe.lastIndex = 0;
    if (string[this._id]) {
      string[this._id]--;
      this._regexn[string[this._id]].lastIndex = 0;
    }
    return this;
  };
  Re.prototype.toString = function () {
    return this.compile()._regexn.map(function (re) {
      return re.source;
    }).join('|');
  };

  module.exports = Re;
})();<commit_msg>proxy options over<commit_after>/*
 * Copyright (C) 2014 Scott Beck, all rights reserved
 *
 * Licensed under the MIT license
 *
 */
(function () {
  'use strict';
  var _ = require("lodash"),
    ReSpec = require('./respec.js')(Re);
  Re.registered = {
    hspace:          /[ \t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]/,
    noHspace:        /[^ \t\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]/,
    vspace:          /[\v\n\r\f]/,
    number:          /0b[01]+|0o[0-7]+|0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?/,
    noVspace:        /[^\v\n\r\f]/,
    space:           /\s/,
    noSpace:         /\S/,
    word:            /\w/,
    noWord:          /\W/,
    eol:             /(?:\r\n?|\n|\f)/,
    notEol:          /[^\r\n\f]/,
    end:             /$/,
    begin:           /^/,
    cMultiComment:   /\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//
  };
  /** Represents a Regular Matching Spec.
   * Makes it possible to join multiple regular expressions
   * together to form a single match.
   *
   *  var re = new
   *
   *  var re = new Re('foo', /foo/, /bar/)
   *  var re = new Re('foo', {foo: /foo/, bar: /bar/});
   *  var re = new Re('foo', [/foo/, /bar/]);
   *
   *  var re = new Re({foo: /foo/, bar: /bar/});
   *  var re = new Re([/foo/, /bar/]);
   *  var re = new Re([/foo/, /bar/]);
   */
  function genguid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
  }
  function Re(namespace) {
    this.opt = _.clone(Re.defaultOptions);
    var args = _.toArray(arguments);
    this._id = genguid();

    if (_.isString(namespace) || _.isNull(namespace) || _.isUndefined(namespace))
      args = args.slice(1);
    else
      namespace = undefined;

    this._spec = new Re.Spec(namespace, args);
    this._regexn = null;
    this.lastMatch = 0;
  }
  Re.Spec = ReSpec;
  Re.defaultOptions = {
    nsDelimiter: '.'
  };
  Re.register = function (identifier) {
    if (!_.isString(identifier)) {
      _.forEach(identifier, function(val, key) {
          if (!_.isString(key))
            throw new Error("invalid identifier for registerMatch '" + identifier + "'");
          Re.register(key, val);
      });
      return Re;
    }
    if (_.has(Re.registered, identifier))
      throw new Error(identifier + " already in use");

    Re.registered[identifier] = Re.render.apply(Re, _.toArray(arguments).slice(1));

    return Re;
  };
  Re.getRegistered = function (identifier) {
    return Re.registered[identifier];
  };
  Re.canJoin = function (re1, re2) {
    return re1.ignoreCase === re2.ignoreCase && re1.multiline === re2.multiline;
  };
  Re.join = function (regexn) {
    var src = regexn.map(function (re) {
      return re.source;
    }).join(')|(');
    src = '(' + src + ')';
    var flags = '';
    if (regexn[0].ignoreCase) flags += 'i';
    if (regexn[0].multiline) flags += 'm';
    flags += 'g';
    return new RegExp(src, flags);
  };
  Re.prototype.options = function (opt) {
    if (opt) _.merge(this._opt, opt);
    this._spec.options(opt);
    return this._opt;
  };
  Re.prototype.namespace = function (namespace) {
    if (namespace) {
      this._namespace = namespace;
      if (this._regexn)
        this.compile(true);
    }
    return this._namespace;
  };
  Re.prototype.compile = function (force) {
    if (!force && this._regexn) return this._regexn;
    this._regexn = this._spec.compile();
    return this;
  };
  Re.prototype.add = function () {
    this._spec.add(_.toArray(arguments));
    return this;
  };
  Re.prototype.addNamed = function (name) {
    var args = _.toArray(arguments).slice(1);
    this._spec.add({name: name, re: args});
    return this;
  };
  Re.prototype.hasSpecs = function () {
    return this._spec.hasSpecs();
  };
  Re.prototype.exec = function (string) {
    this.compile();
    var regexn = this._regexn,
      startRe = string[this._id] || 0,
      found = false,
      that = this;
    _.forEach(regexn.slice(startRe), function(re, i) {
      var match = re.exec(string);
      if (match) {
        _.forEach(that._spec.resolveMatch(match, i), function (res) {
          if (_.some(res[0], _.isString)) {
            found = res.concat(re.lastIndex);
            return false;
          }
        });
        if (found) {
          string[that._id] = i;
          return false;
        }
      }
    });

    if (!found)
      delete string[this._id];
    return found;
  };
  Re.prototype.test = function (string) {
    this.compile();
    var regexn = this._regexn,
      startRe = this.lastMatch,
      match = false, that = this;
    _.forEach(regexn.slice(startRe), function(re, i) {
       match = re.test(string);
       if (match) that.lastMatch = i;
       return !match;
    });
    return match;
  };
  Re.prototype.reject = function (string) {
    if (!this._regexn)
      throw new Error('reject on non-compiled regexp');
    var lastRe = this._regexn[string[this._id] || 0];
    lastRe.lastIndex = 0;
    if (string[this._id]) {
      string[this._id]--;
      this._regexn[string[this._id]].lastIndex = 0;
    }
    return this;
  };
  Re.prototype.toString = function () {
    return this.compile()._regexn.map(function (re) {
      return re.source;
    }).join('|');
  };

  module.exports = Re;
})();<|endoftext|>