<commit_before>//
// Backbone-associations.js 0.3.0
//
// (c) 2012 Dhruva Ray, Jaynti Kanani
// Backbone-associations may be freely distributed under the MIT license;
// see the accompanying LICENSE.txt.
// Depends on [Backbone](https://github.com/documentcloud/backbone)
// and [Underscore](https://github.com/documentcloud/underscore/) as well.
// A complete [Test & Benchmark Suite](../test/test-suite.html) is included for your perusal.

// Initial Setup
// --------------
(function () {
    "use strict";

    // The top-level namespace. All public Backbone classes and modules will be attached to this.
    // Exported for the browser and CommonJS.
    var _, Backbone, BackboneModel, defaultEvents, AssociatedModel;
    if (typeof require !== 'undefined') {
        _ = require('underscore');
        Backbone = require('backbone');
        exports = module.exports = Backbone;
    } else {
        _ = window._;
        Backbone = window.Backbone;
    }
    // Create local reference `Model` prototype.
    BackboneModel = Backbone.Model.prototype;
    // Built-in Backbone `events`.
    defaultEvents = ["change", "add", "remove", "reset", "destroy", "sync", "error", "sort", "request"];

    // Backbone.AssociatedModel
    // --------------

    //Add `Many` and `One` relations to Backbone Object.
    Backbone.Many = "Many";
    Backbone.One = "One";
    // Define `AssociatedModel` (Extends Backbone.Model).
    AssociatedModel = Backbone.AssociatedModel = Backbone.Model.extend({
        // Define relations with Associated Model.
        relations:undefined,
        // Define `Model` property which can keep track of already fired `events`,
        // and prevent redundant event to be triggered in case of circular model graph.
        _proxyCalls:undefined,
        // Set a hash of model attributes on the object,
        // fire Backbone `event` with options.
        // It maintains relations between models during the set operation.
        // It also bubbles up child events to the parent.
        set:function (key, value, options) {
            var attributes, processedRelations, tbp, attr;
            // Duplicate backbone's behavior to allow separate key/value parameters,
            // instead of a single 'attributes' object.
            if (_.isObject(key) || key == null) {
                attributes = key;
                options = value;
            } else {
                attributes = {};
                attributes[key] = value;
            }
            // Extract attributes and options.
            options || (options = {});
            if (!attributes) return this;
            if (options.unset) for (attr in attributes) attributes[attr] = void 0;

            if (this.relations) {
                // Iterate over `this.relations` and `set` model and collection values
                // if `relations` are available.
                _.each(this.relations, function (relation) {
                    var relationKey = relation.key, relatedModel = relation.relatedModel,
                        collectionType = relation.collectionType,
                        val, relationOptions, data;
                    if (attributes[relationKey]) {
                        //Get value of attribute with relation key in `val`.
                        val = _.result(attributes, relationKey);
                        // Get class if relation is stored as a string.
                        relatedModel && _.isString(relatedModel) && (relatedModel = eval(relatedModel));
                        collectionType && _.isString(collectionType) && (collectionType = eval(collectionType));
                        // Merge in `options` specific to this relation.
                        relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
                        // If `relation.type` is `Backbone.Many`,
                        // create `Backbone.Collection` with passed data and perform Backbone `set`.
                        if (relation.type === Backbone.Many) {
                            // `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
                            if (collectionType && !collectionType.prototype instanceof Backbone.Collection) {
                                throw new Error('collectionType must inherit from Backbone.Collection');
                            }

                            if (val instanceof Backbone.Collection) {
                                BackboneModel.set.call(this, relationKey, val, relationOptions);
                            } else if (!this.attributes[relationKey]) {
                                // If `attributes` has no property present,
                                // create `Collection` having `relation.collectionType` as type and
                                // `relation.Model` as model reference, and perform Backbone `set`.
                                data = collectionType ? new collectionType() : this._createCollection(relatedModel);
                                data.add(val, relationOptions);
                                BackboneModel.set.call(this, relationKey, data, relationOptions);
                            } else {
                                this.attributes[relationKey].reset(val, relationOptions);
                            }

                        } else if (relation.type === Backbone.One && relatedModel) {
                            // If passed data is not instance of `Backbone.AssociatedModel`,
                            // create `AssociatedModel` and perform backbone `set`.
                            data = val instanceof AssociatedModel ? val : new relatedModel(val);
                            BackboneModel.set.call(this, relationKey, data, relationOptions)
                        }

                        // Add proxy events to respective parents.
                        // Only add callback if not defined.
                        if (!this.attributes[relationKey]._proxyCallback) {
                            this.attributes[relationKey]._proxyCallback = function () {
                                var args = arguments,
                                    opt = args[0].split(":"),
                                    eventType = opt[0],
                                    eventObject = args[1],
                                    indexEventObject = -1,
                                    _proxyCalls = this.attributes[relationKey]._proxyCalls,
                                    eventPath,
                                    eventAvailable;

                                // Change the event name to a fully qualified path.
                                if (_.contains(defaultEvents, eventType)) {
                                    if (opt && _.size(opt) > 1) {
                                        eventPath = opt[1];
                                    }
                                    // Find the specific object in the collection which has changed.
                                    if (this.attributes[relationKey] instanceof Backbone.Collection && "change" === eventType) {
                                        indexEventObject = _.indexOf(this.attributes[relationKey].models, eventObject);
                                    }
                                    // Manipulate `eventPath`.
                                    eventPath = relationKey + (indexEventObject !== -1 ?
                                        "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
                                    args[0] = eventType + ":" + eventPath;

                                    if (_proxyCalls) {
                                        // If event has been already triggered as result of same source `eventPath`,
                                        // no need to re-trigger event to prevent cycle.
                                        eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
                                            // `event` ends with eventKey.
                                            var d = eventPath.length - eventKey.length;
                                            return eventPath.indexOf(eventKey, d) !== -1;
                                        });
                                        if (eventAvailable) {
                                            return this;
                                        }
                                    } else {
                                        _proxyCalls = this.attributes[relationKey]._proxyCalls = {};
                                    }
                                    // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
                                    _proxyCalls[eventPath] = true;
                                }

                                // Bubble up event to parent `model` with new changed arguments.
                                this.trigger.apply(this, args);

                                // Remove `eventPath` from `_proxyCalls`,
                                // if `eventPath` and `_proxCalls` are available,
                                // which allow event to be triggered on for next operation of `set`.
                                if (eventPath && _proxyCalls) {
                                    delete _proxyCalls[eventPath];
                                }
                                return this;
                            };
                            this.attributes[relationKey].on("all", this.attributes[relationKey]._proxyCallback, this);
                        }

                        // Create a local `processedRelations` array to store the relation key which has been processed.
                        // We cannot use `this.relations` because if there is no value defined for `relationKey`,
                        // it will not get processed by either `BackboneModel` `set` or the `AssociatedModel` `set`.
                        !processedRelations && (processedRelations = []);
                        if (_.indexOf(processedRelations, relationKey) === -1) {
                            processedRelations.push(relationKey);
                        }
                    }
                }, this);
            }
            if (processedRelations) {
                // Find attributes yet to be processed - `tbp`.
                tbp = {};
                for (attr in attributes) {
                    if (_.indexOf(processedRelations, attr) === -1) {
                        tbp[attr] = attributes[attr];
                    }
                }
            } else {
                // Set all `attributes` to `tbp`.
                tbp = attributes;
            }
            // Return results for `BackboneModel.set`.
            return BackboneModel.set.call(this, tbp, options);
        },
        // Returns New `collection` of type `relation.relatedModel`.
        _createCollection:function (type) {
            var collection, relatedModel = type;
            _.isString(relatedModel) && (relatedModel = eval(relatedModel));
            // Creates new `Backbone.Collection` and defines model class.
            if (relatedModel && relatedModel.prototype instanceof AssociatedModel) {
                collection = new Backbone.Collection();
                collection.model = relatedModel;
            } else {
                throw new Error('type must inherit from Backbone.AssociatedModel');
            }
            return collection;
        },
        // Has the model changed. Traverse the object hierarchy to compute dirtyness.
        hasChanged:function (attr) {
            var isDirty, relation, attrValue, i, dirtyObjects;
            // To prevent cycles, check if this node is visited.
            if (!this.visitedHC) {
                this.visitedHC = true;
                isDirty = BackboneModel.hasChanged.apply(this, arguments);
                if (!isDirty && this.relations) {
                    // Go down the hierarchy to see if anything has `changed`.
                    for (i = 0; i < this.relations.length; ++i) {
                        relation = this.relations[i];
                        attrValue = this.attributes[relation.key];
                        if (attrValue) {
                            if (attrValue instanceof Backbone.Collection) {
                                dirtyObjects = attrValue.filter(function (m) {
                                    return m.hasChanged() === true;
                                });
                                _.size(dirtyObjects) > 0 && (isDirty = true);
                            } else {
                                isDirty = attrValue.hasChanged && attrValue.hasChanged();
                            }
                            if (isDirty) {
                                break;
                            }
                        }
                    }
                }
                delete this.visitedHC;
            }
            return !!isDirty;
        },
        // Returns a hash of the changed attributes.
        changedAttributes:function (diff) {
            var delta, relation, attrValue, changedCollection, i;
            // To prevent cycles, check if this node is visited.
            if (!this.visited) {
                this.visited = true;
                delta = BackboneModel.changedAttributes.apply(this, arguments);
                if (this.relations) {
                    for (i = 0; i < this.relations.length; ++i) {
                        relation = this.relations[i];
                        attrValue = this.attributes[relation.key];
                        if (attrValue) {
                            if (attrValue instanceof Backbone.Collection) {
                                changedCollection = _.filter(attrValue.map(function (m) {
                                    return m.changedAttributes();
                                }), function (m) {
                                    return !!m;
                                });
                                if (_.size(changedCollection) > 0) {
                                    delta[relation.key] = changedCollection;
                                }
                            } else if (attrValue instanceof AssociatedModel && attrValue.hasChanged()) {
                                delta[relation.key] = attrValue.toJSON();
                            }
                        }
                    }
                }
                delete this.visited;
            }
            return !delta ? false : delta;
        },
        // Returns the hash of the previous attributes of the graph.
        previousAttributes:function () {
            var pa, attrValue, pattrValue, pattrJSON;
            // To prevent cycles, check if this node is visited.
            if (!this.visited) {
                this.visited = true;
                pa = BackboneModel.previousAttributes.apply(this, arguments);
                if (this.relations) {
                    _.each(this.relations, function (relation) {
                        attrValue = this.attributes[relation.key];
                        pattrValue = pa[relation.key];
                        pattrJSON = pattrValue ? pattrValue.toJSON() : undefined;
                        if (pattrValue && pattrValue == attrValue) {
                            if (attrValue instanceof AssociatedModel) {
                                pa[relation.key] = attrValue.previousAttributes();
                            } else if (attrValue instanceof Backbone.Collection) {
                                pa[relation.key] = attrValue.map(function (m) {
                                    return m.previousAttributes();
                                });
                            }
                        } else {
                            if (pattrValue)
                                pa[relation.key] = pattrJSON;
                        }
                    }, this);
                }
                delete this.visited;
            }
            return pa;
        },
        // Return the previous value of the passed in attribute.
        previous:function (attr) {
            return this.previousAttributes()[attr];
        },
        // The JSON representation of the model.
        toJSON:function (options) {
            var json, aJson;
            if (!this.visited) {
                this.visited = true;
                // Get json representation from `BackboneModel.toJSON`.
                json = BackboneModel.toJSON.apply(this, arguments);
                // If `this.relations` is defined, iterate through each `relation`
                // and added it's json representation to parents' json representation.
                if (this.relations) {
                    _.each(this.relations, function (relation) {
                        var attr = this.attributes[relation.key];
                        if (attr) {
                            aJson = attr.toJSON(options);
                            json[relation.key] = _.isArray(aJson) ? _.compact(aJson) : aJson;
                        }
                    }, this);
                }
                delete this.visited;
            }
            return json;
        },
        // Deep `clone` the model.
        clone:function () {
            var cloneObj, newCollection;
            if (!this.visited) {
                this.visited = true;
                // Get shallow clone from `BackboneModel.clone`.
                cloneObj = BackboneModel.clone.apply(this, arguments);
                // If `this.relations` is defined, iterate through each `relation` and `clone`.
                if (this.relations) {
                    _.each(this.relations, function (relation) {
                        if (this.attributes[relation.key]) {
                            var sourceObj = cloneObj.attributes[relation.key];
                            if (sourceObj instanceof Backbone.Collection) {
                                // Creates new `collection` using `relation`.
                                newCollection = relation.collectionType ? new relation.collectionType() :
                                    this._createCollection(relation.relatedModel);
                                // Added each `clone` model to `newCollection`.
                                sourceObj.each(function (model) {
                                    var mClone = model.clone();
                                    mClone && newCollection.add(mClone);
                                });
                                cloneObj.attributes[relation.key] = newCollection;
                            } else if (sourceObj instanceof Backbone.Model) {
                                cloneObj.attributes[relation.key] = sourceObj.clone();
                            }
                        }
                    }, this);
                }
                delete this.visited;
            }
            return cloneObj;
        }
    });
})();<commit_msg>add getPathArray and getAttr method to get/set qualified path<commit_after>//
// Backbone-associations.js 0.3.0
//
// (c) 2012 Dhruva Ray, Jaynti Kanani
// Backbone-associations may be freely distributed under the MIT license;
// see the accompanying LICENSE.txt.
// Depends on [Backbone](https://github.com/documentcloud/backbone)
// and [Underscore](https://github.com/documentcloud/underscore/) as well.
// A complete [Test & Benchmark Suite](../test/test-suite.html) is included for your perusal.

// Initial Setup
// --------------
(function () {
    "use strict";

    // The top-level namespace. All public Backbone classes and modules will be attached to this.
    // Exported for the browser and CommonJS.
    var _, Backbone, BackboneModel, defaultEvents, AssociatedModel;
    if (typeof require !== 'undefined') {
        _ = require('underscore');
        Backbone = require('backbone');
        exports = module.exports = Backbone;
    } else {
        _ = window._;
        Backbone = window.Backbone;
    }
    // Create local reference `Model` prototype.
    BackboneModel = Backbone.Model.prototype;
    // Built-in Backbone `events`.
    defaultEvents = ["change", "add", "remove", "reset", "destroy", "sync", "error", "sort", "request"];

    // Backbone.AssociatedModel
    // --------------

    //Add `Many` and `One` relations to Backbone Object.
    Backbone.Many = "Many";
    Backbone.One = "One";
    // Define `AssociatedModel` (Extends Backbone.Model).
    AssociatedModel = Backbone.AssociatedModel = Backbone.Model.extend({
        // Define relations with Associated Model.
        relations:undefined,
        // Define `Model` property which can keep track of already fired `events`,
        // and prevent redundant event to be triggered in case of circular model graph.
        _proxyCalls:undefined,
        // Set a hash of model attributes on the object,
        // fire Backbone `event` with options.
        // It maintains relations between models during the set operation.
        // It also bubbles up child events to the parent.
        set:function (key, value, options) {
            var attributes, processedRelations, tbp, attr;
            // Duplicate backbone's behavior to allow separate key/value parameters,
            // instead of a single 'attributes' object.
            if (_.isObject(key) || key == null) {
                attributes = key;
                options = value;
            } else {
                attributes = {};
                attributes[key] = value;
            }
            // Extract attributes and options.
            options || (options = {});
            if (!attributes) return this;
            if (options.unset) for (attr in attributes) attributes[attr] = void 0;

            if (this.relations) {
                // Iterate over `this.relations` and `set` model and collection values
                // if `relations` are available.
                _.each(this.relations, function (relation) {
                    var relationKey = relation.key, relatedModel = relation.relatedModel,
                        collectionType = relation.collectionType,
                        val, relationOptions, data;
                    if (attributes[relationKey]) {
                        //Get value of attribute with relation key in `val`.
                        val = _.result(attributes, relationKey);
                        // Get class if relation is stored as a string.
                        relatedModel && _.isString(relatedModel) && (relatedModel = eval(relatedModel));
                        collectionType && _.isString(collectionType) && (collectionType = eval(collectionType));
                        // Merge in `options` specific to this relation.
                        relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
                        // If `relation.type` is `Backbone.Many`,
                        // create `Backbone.Collection` with passed data and perform Backbone `set`.
                        if (relation.type === Backbone.Many) {
                            // `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
                            if (collectionType && !collectionType.prototype instanceof Backbone.Collection) {
                                throw new Error('collectionType must inherit from Backbone.Collection');
                            }

                            if (val instanceof Backbone.Collection) {
                                BackboneModel.set.call(this, relationKey, val, relationOptions);
                            } else if (!this.attributes[relationKey]) {
                                // If `attributes` has no property present,
                                // create `Collection` having `relation.collectionType` as type and
                                // `relation.Model` as model reference, and perform Backbone `set`.
                                data = collectionType ? new collectionType() : this._createCollection(relatedModel);
                                data.add(val, relationOptions);
                                BackboneModel.set.call(this, relationKey, data, relationOptions);
                            } else {
                                this.attributes[relationKey].reset(val, relationOptions);
                            }

                        } else if (relation.type === Backbone.One && relatedModel) {
                            // If passed data is not instance of `Backbone.AssociatedModel`,
                            // create `AssociatedModel` and perform backbone `set`.
                            data = val instanceof AssociatedModel ? val : new relatedModel(val);
                            BackboneModel.set.call(this, relationKey, data, relationOptions)
                        }

                        // Add proxy events to respective parents.
                        // Only add callback if not defined.
                        if (!this.attributes[relationKey]._proxyCallback) {
                            this.attributes[relationKey]._proxyCallback = function () {
                                var args = arguments,
                                    opt = args[0].split(":"),
                                    eventType = opt[0],
                                    eventObject = args[1],
                                    indexEventObject = -1,
                                    _proxyCalls = this.attributes[relationKey]._proxyCalls,
                                    eventPath,
                                    eventAvailable;

                                // Change the event name to a fully qualified path.
                                if (_.contains(defaultEvents, eventType)) {
                                    if (opt && _.size(opt) > 1) {
                                        eventPath = opt[1];
                                    }
                                    // Find the specific object in the collection which has changed.
                                    if (this.attributes[relationKey] instanceof Backbone.Collection && "change" === eventType) {
                                        indexEventObject = _.indexOf(this.attributes[relationKey].models, eventObject);
                                    }
                                    // Manipulate `eventPath`.
                                    eventPath = relationKey + (indexEventObject !== -1 ?
                                        "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
                                    args[0] = eventType + ":" + eventPath;

                                    if (_proxyCalls) {
                                        // If event has been already triggered as result of same source `eventPath`,
                                        // no need to re-trigger event to prevent cycle.
                                        eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
                                            // `event` ends with eventKey.
                                            var d = eventPath.length - eventKey.length;
                                            return eventPath.indexOf(eventKey, d) !== -1;
                                        });
                                        if (eventAvailable) {
                                            return this;
                                        }
                                    } else {
                                        _proxyCalls = this.attributes[relationKey]._proxyCalls = {};
                                    }
                                    // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
                                    _proxyCalls[eventPath] = true;
                                }

                                // Bubble up event to parent `model` with new changed arguments.
                                this.trigger.apply(this, args);

                                // Remove `eventPath` from `_proxyCalls`,
                                // if `eventPath` and `_proxCalls` are available,
                                // which allow event to be triggered on for next operation of `set`.
                                if (eventPath && _proxyCalls) {
                                    delete _proxyCalls[eventPath];
                                }
                                return this;
                            };
                            this.attributes[relationKey].on("all", this.attributes[relationKey]._proxyCallback, this);
                        }

                        // Create a local `processedRelations` array to store the relation key which has been processed.
                        // We cannot use `this.relations` because if there is no value defined for `relationKey`,
                        // it will not get processed by either `BackboneModel` `set` or the `AssociatedModel` `set`.
                        !processedRelations && (processedRelations = []);
                        if (_.indexOf(processedRelations, relationKey) === -1) {
                            processedRelations.push(relationKey);
                        }
                    }
                }, this);
            }
            if (processedRelations) {
                // Find attributes yet to be processed - `tbp`.
                tbp = {};
                for (attr in attributes) {
                    if (_.indexOf(processedRelations, attr) === -1) {
                        tbp[attr] = attributes[attr];
                    }
                }
            } else {
                // Set all `attributes` to `tbp`.
                tbp = attributes;
            }
            // Return results for `BackboneModel.set`.
            return BackboneModel.set.call(this, tbp, options);
        },
        // Returns New `collection` of type `relation.relatedModel`.
        _createCollection:function (type) {
            var collection, relatedModel = type;
            _.isString(relatedModel) && (relatedModel = eval(relatedModel));
            // Creates new `Backbone.Collection` and defines model class.
            if (relatedModel && relatedModel.prototype instanceof AssociatedModel) {
                collection = new Backbone.Collection();
                collection.model = relatedModel;
            } else {
                throw new Error('type must inherit from Backbone.AssociatedModel');
            }
            return collection;
        },
        // Has the model changed. Traverse the object hierarchy to compute dirtyness.
        hasChanged:function (attr) {
            var isDirty, relation, attrValue, i, dirtyObjects;
            // To prevent cycles, check if this node is visited.
            if (!this.visitedHC) {
                this.visitedHC = true;
                isDirty = BackboneModel.hasChanged.apply(this, arguments);
                if (!isDirty && this.relations) {
                    // Go down the hierarchy to see if anything has `changed`.
                    for (i = 0; i < this.relations.length; ++i) {
                        relation = this.relations[i];
                        attrValue = this.attributes[relation.key];
                        if (attrValue) {
                            if (attrValue instanceof Backbone.Collection) {
                                dirtyObjects = attrValue.filter(function (m) {
                                    return m.hasChanged() === true;
                                });
                                _.size(dirtyObjects) > 0 && (isDirty = true);
                            } else {
                                isDirty = attrValue.hasChanged && attrValue.hasChanged();
                            }
                            if (isDirty) {
                                break;
                            }
                        }
                    }
                }
                delete this.visitedHC;
            }
            return !!isDirty;
        },
        // Returns a hash of the changed attributes.
        changedAttributes:function (diff) {
            var delta, relation, attrValue, changedCollection, i;
            // To prevent cycles, check if this node is visited.
            if (!this.visited) {
                this.visited = true;
                delta = BackboneModel.changedAttributes.apply(this, arguments);
                if (this.relations) {
                    for (i = 0; i < this.relations.length; ++i) {
                        relation = this.relations[i];
                        attrValue = this.attributes[relation.key];
                        if (attrValue) {
                            if (attrValue instanceof Backbone.Collection) {
                                changedCollection = _.filter(attrValue.map(function (m) {
                                    return m.changedAttributes();
                                }), function (m) {
                                    return !!m;
                                });
                                if (_.size(changedCollection) > 0) {
                                    delta[relation.key] = changedCollection;
                                }
                            } else if (attrValue instanceof AssociatedModel && attrValue.hasChanged()) {
                                delta[relation.key] = attrValue.toJSON();
                            }
                        }
                    }
                }
                delete this.visited;
            }
            return !delta ? false : delta;
        },
        // Returns the hash of the previous attributes of the graph.
        previousAttributes:function () {
            var pa, attrValue, pattrValue, pattrJSON;
            // To prevent cycles, check if this node is visited.
            if (!this.visited) {
                this.visited = true;
                pa = BackboneModel.previousAttributes.apply(this, arguments);
                if (this.relations) {
                    _.each(this.relations, function (relation) {
                        attrValue = this.attributes[relation.key];
                        pattrValue = pa[relation.key];
                        pattrJSON = pattrValue ? pattrValue.toJSON() : undefined;
                        if (pattrValue && pattrValue == attrValue) {
                            if (attrValue instanceof AssociatedModel) {
                                pa[relation.key] = attrValue.previousAttributes();
                            } else if (attrValue instanceof Backbone.Collection) {
                                pa[relation.key] = attrValue.map(function (m) {
                                    return m.previousAttributes();
                                });
                            }
                        } else {
                            if (pattrValue)
                                pa[relation.key] = pattrJSON;
                        }
                    }, this);
                }
                delete this.visited;
            }
            return pa;
        },
        // Return the previous value of the passed in attribute.
        previous:function (attr) {
            return this.previousAttributes()[attr];
        },
        // The JSON representation of the model.
        toJSON:function (options) {
            var json, aJson;
            if (!this.visited) {
                this.visited = true;
                // Get json representation from `BackboneModel.toJSON`.
                json = BackboneModel.toJSON.apply(this, arguments);
                // If `this.relations` is defined, iterate through each `relation`
                // and added it's json representation to parents' json representation.
                if (this.relations) {
                    _.each(this.relations, function (relation) {
                        var attr = this.attributes[relation.key];
                        if (attr) {
                            aJson = attr.toJSON(options);
                            json[relation.key] = _.isArray(aJson) ? _.compact(aJson) : aJson;
                        }
                    }, this);
                }
                delete this.visited;
            }
            return json;
        },
        // Deep `clone` the model.
        clone:function () {
            var cloneObj, newCollection;
            if (!this.visited) {
                this.visited = true;
                // Get shallow clone from `BackboneModel.clone`.
                cloneObj = BackboneModel.clone.apply(this, arguments);
                // If `this.relations` is defined, iterate through each `relation` and `clone`.
                if (this.relations) {
                    _.each(this.relations, function (relation) {
                        if (this.attributes[relation.key]) {
                            var sourceObj = cloneObj.attributes[relation.key];
                            if (sourceObj instanceof Backbone.Collection) {
                                // Creates new `collection` using `relation`.
                                newCollection = relation.collectionType ? new relation.collectionType() :
                                    this._createCollection(relation.relatedModel);
                                // Added each `clone` model to `newCollection`.
                                sourceObj.each(function (model) {
                                    var mClone = model.clone();
                                    mClone && newCollection.add(mClone);
                                });
                                cloneObj.attributes[relation.key] = newCollection;
                            } else if (sourceObj instanceof Backbone.Model) {
                                cloneObj.attributes[relation.key] = sourceObj.clone();
                            }
                        }
                    }, this);
                }
                delete this.visited;
            }
            return cloneObj;
        },

        // Get the value of an attribute with `path`.
        getAttr: function (path, iterator) {
            var result = this,
                attrs = getPathArray(path),
                key,
                i;
            iterator || (iterator = function (memo, key) {
                return memo instanceof Backbone.Collection && _.isNumber(key) ? memo.at(key) : memo.attributes[key];
            });
            for (i = 0; i < attrs.length; i++) {
                key = attrs[i];
                if (!result) break;
                result = iterator.call(this, result, key, attrs);
            }
            return result;
        }
    });

    // Get Path `attrs` as Array
    // Example:
    //  'employee.works_for.locations[2].name' -> ['employee', 'works_for', 'locations', 2, 'name']
    var getPathArray = function (path, iterator, context) {
        if (_.isString(path)) {
            iterator || (iterator = function (value) {
                return value.match(/^\d+$/) ? parseInt(value, 10) : value;
            });
            return _.map(path.match(/[^\.\[\]]+/g) || [], iterator, context);
        }
        return path || [];
    }
})();<|endoftext|>


<commit_before>/*!
 * Backbone Named Routes 0.1.3
 * http://github.com/drtangible/backbone-named-routes
 */

(function(){

  var PATTERNS = {};

  var addRoute = function(name, route, options) {
    options = options || {};

    _.defaults(options, {
      includeRoot: false
    });

    // Create key to store path patterns for this route name.
    PATTERNS[name] = PATTERNS[name] || {};

    // Store the route pattern for the combination of this route name and the
    // number of path params defined in the pattern.
    var numberOfParams = route.match(/\:\w+/g) ? route.match(/\:\w+/g).length : 0;
    PATTERNS[name][numberOfParams] = route;

    // Create the named route helper method for this route name.
    Backbone.NamedRoutes[name + 'Path'] = function() {
      var args = Array.prototype.slice.call(arguments);
      var hasQueryParams = _(args[args.length-1]).isObject();
      var numberOfParams = hasQueryParams ? arguments.length - 1 : arguments.length;
      var routePattern = PATTERNS[name][numberOfParams];
      var queryParams = hasQueryParams ? args.pop() : null;

      if (options.includeRoot) routePattern = prependRoot(routePattern);

      return pathFor(routePattern, args, queryParams);
    };
  };

  var prependRoot = function(route) {
    var history = Backbone.history;
    if (!history) return route;
    if (!history.options) return route;

    routeWithRoot = history.options.root + '/' + route;
    return routeWithRoot.replace("//", "/");
  };

  var pathFor = function(pathPattern, urlParams, queryParams) {
    var path = pathPattern;
    if (path.charAt(0) !== "/") path = "/" + path;

    for(var i = 0; i < urlParams.length; i++) {
      var param = urlParams[i];
      path = path.replace(/\:\w+/, param);
    }

    var filteredQueryParams = filterObject(queryParams);

    if (filteredQueryParams && !_.isEmpty(filteredQueryParams)) {
      path += "?" + $.param(filteredQueryParams);
    }
    return path;
  };

  // Filters out `undefined` and `null` values
  var filterObject = function(object) {
    var filteredObject = {}, key;

    for(key in object) {
      if (has(object, key)) {
        if (object[key] != null) {
          filteredObject[key] = object[key];
        }
      }
    }

    return filteredObject;
  };

  // cache reference to `Object.prototype.hasOwnProperty`
  var hasOwnProperty = Object.prototype.hasOwnProperty;

  var has = function(object, key) {
    return hasOwnProperty.call(object, key);
  };

  Backbone.NamedRoutes = {

    VERSION: '0.1.4',

    addRoute: addRoute
  };

  _(Backbone.View.prototype).extend({
    helper: Backbone.NamedRoutes
  });

  _(Backbone.Router.prototype).extend({
    helper: Backbone.NamedRoutes
  });

  _(Backbone.Router.prototype).extend({
    route: (function(original) {
      return function(route, name, callback) {
        addRoute(name, route, { includeRoot: true });
        original.apply(this, arguments);
      };
    }(Backbone.Router.prototype.route))
  });

}).call(this);
<commit_msg>Version bump.<commit_after>/*!
 * Backbone Named Routes 0.1.4
 * http://github.com/drtangible/backbone-named-routes
 */

(function(){

  var PATTERNS = {};

  var addRoute = function(name, route, options) {
    options = options || {};

    _.defaults(options, {
      includeRoot: false
    });

    // Create key to store path patterns for this route name.
    PATTERNS[name] = PATTERNS[name] || {};

    // Store the route pattern for the combination of this route name and the
    // number of path params defined in the pattern.
    var numberOfParams = route.match(/\:\w+/g) ? route.match(/\:\w+/g).length : 0;
    PATTERNS[name][numberOfParams] = route;

    // Create the named route helper method for this route name.
    Backbone.NamedRoutes[name + 'Path'] = function() {
      var args = Array.prototype.slice.call(arguments);
      var hasQueryParams = _(args[args.length-1]).isObject();
      var numberOfParams = hasQueryParams ? arguments.length - 1 : arguments.length;
      var routePattern = PATTERNS[name][numberOfParams];
      var queryParams = hasQueryParams ? args.pop() : null;

      if (options.includeRoot) routePattern = prependRoot(routePattern);

      return pathFor(routePattern, args, queryParams);
    };
  };

  var prependRoot = function(route) {
    var history = Backbone.history;
    if (!history) return route;
    if (!history.options) return route;

    routeWithRoot = history.options.root + '/' + route;
    return routeWithRoot.replace("//", "/");
  };

  var pathFor = function(pathPattern, urlParams, queryParams) {
    var path = pathPattern;
    if (path.charAt(0) !== "/") path = "/" + path;

    for(var i = 0; i < urlParams.length; i++) {
      var param = urlParams[i];
      path = path.replace(/\:\w+/, param);
    }

    var filteredQueryParams = filterObject(queryParams);

    if (filteredQueryParams && !_.isEmpty(filteredQueryParams)) {
      path += "?" + $.param(filteredQueryParams);
    }
    return path;
  };

  // Filters out `undefined` and `null` values
  var filterObject = function(object) {
    var filteredObject = {}, key;

    for(key in object) {
      if (has(object, key)) {
        if (object[key] != null) {
          filteredObject[key] = object[key];
        }
      }
    }

    return filteredObject;
  };

  // cache reference to `Object.prototype.hasOwnProperty`
  var hasOwnProperty = Object.prototype.hasOwnProperty;

  var has = function(object, key) {
    return hasOwnProperty.call(object, key);
  };

  Backbone.NamedRoutes = {

    VERSION: '0.1.4',

    addRoute: addRoute
  };

  _(Backbone.View.prototype).extend({
    helper: Backbone.NamedRoutes
  });

  _(Backbone.Router.prototype).extend({
    helper: Backbone.NamedRoutes
  });

  _(Backbone.Router.prototype).extend({
    route: (function(original) {
      return function(route, name, callback) {
        addRoute(name, route, { includeRoot: true });
        original.apply(this, arguments);
      };
    }(Backbone.Router.prototype.route))
  });

}).call(this);
<|endoftext|>


<commit_before>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  ListView,
  TouchableHighlight,
  Text,
  TextInput,
  View,
  Image,
  TabBarIOS
} = React;

var API_BASE_URL = 'http://bartjsonapi.elasticbeanstalk.com/api/';
var STATION_REQUEST_URL = API_BASE_URL + 'stations';
var STATUS_REQUEST_URL = API_BASE_URL + 'status';

var BartReactNative = React.createClass({
  getInitialState: function() {
    return {
      selectedTab: 'departures',
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2
      }),
      systemStatus: {
        traincount: '?',
        message: '',
        time: '?',
      },
      loaded: false
    }
  },

  componentDidMount: function() {
    this.fetchData();
  },

  fetchData: function() {
    fetch(STATION_REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData),
          loaded: true
        });
      })
      .done();

    // TODO change to using a proper bsa call to determine no issues!
    fetch(STATUS_REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          systemStatus: responseData
        });
        console.log(responseData);
      })
      .done();
  },

  // TODO tidy up time formatting rendering!
  render: function() {
    if (! this.state.loaded) {
      return this.renderLoadingView();
    }
    return (
      <View style={styles.pageView}>
        <View style={styles.headerImageView}>
          <Image
            style={styles.headerImage}
            source={require('image!header')}
          />
          <Text style={styles.systemStatus}>{this.state.systemStatus.time.replace('PDT', '').replace('PST', '').replace(' ', '').replace(':00AM', 'AM').replace(':00PM', 'PM').trim()}: {this.state.systemStatus.traincount} trains operating, {this.state.systemStatus.message === '' ? 'no reported issues' : 'system advisory in place'}.</Text>
        </View>
        <View style={styles.stationListContainer}>
          <Text style={styles.textHeader}>BART Departures</Text>
          <View style={styles.stationFilter}>
            <TextInput
              autoCapitalize="none"
              autoCorrect={false}
              clearButtonMode="always"
              placeholder="Filter..."
              value=""
              style={styles.filterTextInput}
              onChangeText={this.searchText.bind(this)}
            />
          </View>
          <ListView
            dataSource = {this.state.dataSource}
            renderRow = {this.renderStation}
            style = {styles.listView}
          />
          <View style={styles.footerView}>
            <Text style={styles.textFooter}>React Native Demo by Simon Prickett</Text>
          </View>
        </View>
      </View>
    );
  },

  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text>
          Loading stations...
        </Text>
      </View>
    );
  },

  renderStation: function(station, sectionId, rowId) {
    return (
      <TouchableHighlight onPress={() => this.pressRow(rowId)}>
        <View style={styles.container}>
          <View style={styles.stationListContainer}>
            <Text style={styles.stationListItem}>{station.name}</Text>
          </View>
        </View>
      </TouchableHighlight>
    );
  },

  searchText: function(text) {
    console.log(text);
  },

  pressRow: function(rowId) {
    console.log('hi from ' + rowId);
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  },

  pageView: {
    flex: 1,
    backgroundColor: '#000000'
  },

  stationListContainer: {
    flex: 1,
    backgroundColor: '#F5FCFF',
    marginLeft: 10,
    marginRight: 10,
    borderRadius: 5,
    borderWidth: 1,
    borderColor: '#F5FCFF'
  },

  stationListItem: {
    fontSize: 20,
    textAlign: 'left',
    borderColor: '#cccccc',
    borderWidth: 1,
    borderRadius: 5,
    padding: 5,
    marginTop: 5
  },

  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  listView: {
    paddingTop: 5,
    backgroundColor: '#F5FCFF',
    borderWidth: 1,
    borderRadius: 5,
    borderColor: '#cccccc',
    margin: 5
  },
  stationFilter: {
    backgroundColor: '#eeeeee',
    paddingTop: 10,
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 10,
    marginLeft: 5,
    marginRight: 5
  },
  filterTextInput: {
    backgroundColor: 'white',
    borderColor: '#cccccc',
    borderRadius: 3,
    borderWidth: 1,
    height: 30,
    paddingLeft: 8
  },
  textHeader: {
    paddingTop: 0,
    fontSize: 25,
    textAlign: 'left',
    margin: 5
  },
  headerImageView: {
    marginTop: 20,
    marginLeft: 10,
    marginRight: 10
  },
  headerImage: {
    width: 355,
    height: 30
  },
  textFooter: {
    color: '#ffffff',
    backgroundColor: '#000000',
    textAlign: 'center',
    fontSize: 15
  },
  footerView: {
    paddingTop: 5,
    paddingBottom: 5,
    backgroundColor: '#000000',
  },
  systemStatus: {
    backgroundColor: '#000000',
    color: '#ffffff',
    textAlign: 'left',
    marginBottom: 3,
    marginTop: 3
  }
});

AppRegistry.registerComponent('BartReactNative', () => BartReactNative);
<commit_msg>Removed spurious tab bar dependency.<commit_after>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  ListView,
  TouchableHighlight,
  Text,
  TextInput,
  View,
  Image
} = React;

var API_BASE_URL = 'http://bartjsonapi.elasticbeanstalk.com/api/';
var STATION_REQUEST_URL = API_BASE_URL + 'stations';
var STATUS_REQUEST_URL = API_BASE_URL + 'status';

var BartReactNative = React.createClass({
  getInitialState: function() {
    return {
      selectedTab: 'departures',
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2
      }),
      systemStatus: {
        traincount: '?',
        message: '',
        time: '?',
      },
      loaded: false
    }
  },

  componentDidMount: function() {
    this.fetchData();
  },

  fetchData: function() {
    fetch(STATION_REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData),
          loaded: true
        });
      })
      .done();

    // TODO change to using a proper bsa call to determine no issues!
    fetch(STATUS_REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          systemStatus: responseData
        });
        console.log(responseData);
      })
      .done();
  },

  // TODO tidy up time formatting rendering!
  render: function() {
    if (! this.state.loaded) {
      return this.renderLoadingView();
    }
    return (
      <View style={styles.pageView}>
        <View style={styles.headerImageView}>
          <Image
            style={styles.headerImage}
            source={require('image!header')}
          />
          <Text style={styles.systemStatus}>{this.state.systemStatus.time.replace('PDT', '').replace('PST', '').replace(' ', '').replace(':00AM', 'AM').replace(':00PM', 'PM').trim()}: {this.state.systemStatus.traincount} trains operating, {this.state.systemStatus.message === '' ? 'no reported issues' : 'system advisory in place'}.</Text>
        </View>
        <View style={styles.stationListContainer}>
          <Text style={styles.textHeader}>BART Departures</Text>
          <View style={styles.stationFilter}>
            <TextInput
              autoCapitalize="none"
              autoCorrect={false}
              clearButtonMode="always"
              placeholder="Filter..."
              value=""
              style={styles.filterTextInput}
              onChangeText={this.searchText.bind(this)}
            />
          </View>
          <ListView
            dataSource = {this.state.dataSource}
            renderRow = {this.renderStation}
            style = {styles.listView}
          />
          <View style={styles.footerView}>
            <Text style={styles.textFooter}>React Native Demo by Simon Prickett</Text>
          </View>
        </View>
      </View>
    );
  },

  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text>
          Loading stations...
        </Text>
      </View>
    );
  },

  renderStation: function(station, sectionId, rowId) {
    return (
      <TouchableHighlight onPress={() => this.pressRow(rowId)}>
        <View style={styles.container}>
          <View style={styles.stationListContainer}>
            <Text style={styles.stationListItem}>{station.name}</Text>
          </View>
        </View>
      </TouchableHighlight>
    );
  },

  searchText: function(text) {
    console.log(text);
  },

  pressRow: function(rowId) {
    console.log('hi from ' + rowId);
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  },

  pageView: {
    flex: 1,
    backgroundColor: '#000000'
  },

  stationListContainer: {
    flex: 1,
    backgroundColor: '#F5FCFF',
    marginLeft: 10,
    marginRight: 10,
    borderRadius: 5,
    borderWidth: 1,
    borderColor: '#F5FCFF'
  },

  stationListItem: {
    fontSize: 20,
    textAlign: 'left',
    borderColor: '#cccccc',
    borderWidth: 1,
    borderRadius: 5,
    padding: 5,
    marginTop: 5
  },

  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  listView: {
    paddingTop: 5,
    backgroundColor: '#F5FCFF',
    borderWidth: 1,
    borderRadius: 5,
    borderColor: '#cccccc',
    margin: 5
  },
  stationFilter: {
    backgroundColor: '#eeeeee',
    paddingTop: 10,
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 10,
    marginLeft: 5,
    marginRight: 5
  },
  filterTextInput: {
    backgroundColor: 'white',
    borderColor: '#cccccc',
    borderRadius: 3,
    borderWidth: 1,
    height: 30,
    paddingLeft: 8
  },
  textHeader: {
    paddingTop: 0,
    fontSize: 25,
    textAlign: 'left',
    margin: 5
  },
  headerImageView: {
    marginTop: 20,
    marginLeft: 10,
    marginRight: 10
  },
  headerImage: {
    width: 355,
    height: 30
  },
  textFooter: {
    color: '#ffffff',
    backgroundColor: '#000000',
    textAlign: 'center',
    fontSize: 15
  },
  footerView: {
    paddingTop: 5,
    paddingBottom: 5,
    backgroundColor: '#000000',
  },
  systemStatus: {
    backgroundColor: '#000000',
    color: '#ffffff',
    textAlign: 'left',
    marginBottom: 3,
    marginTop: 3
  }
});

AppRegistry.registerComponent('BartReactNative', () => BartReactNative);
<|endoftext|>


<commit_before>import style from './options/style';
import event from './options/event';

/** Global options
 *	@public
 *	@namespace options {Object}
 */

function recomputeKey (vnode) {
	var children = vnode.children || [], keyMap = {}, cnt = 0, keyArr = [], last = children.length - 1

	children.forEach(function(child, i) {
		var props = child.attributes || {},
			key = props.key,
			invalidKey = key === recomputeKey.x && ++cnt
		if (invalidKey || key in keyMap || i === last) {
			if (keyArr.length) {
				keyArr.forEach(function(info) {
					info[2].key = info[0].key = '.' + cnt + ':$' + String(info[1]).replace(/^\.[\S]+:\$/g, '')
				})
				keyMap = {}
				keyArr = []
				cnt++
			}
		}
		!invalidKey && keyArr.push([props, keyMap[key] = key, child])
	})
	keyMap = keyArr = null
}

export default {

	/** If `true`, `prop` changes trigger synchronous component updates.
	 *	@name syncComponentUpdates
	 *	@type Boolean
	 *	@default true
	 */
	//syncComponentUpdates: true,

	/** Processes all created VNodes.
	 *	@param {VNode} vnode	A newly-created VNode to normalize/process
	 */
	vnode(vnode) {
		// fork add to support react event sys
		vnode._hostParent = null;
		vnode._hostNode = null;
		vnode._rootNodeID = null;
		recomputeKey(vnode)
	},

	/** Hook for style process */
	style,

	/** Hook for event handle */
	_event: event

	/** Hook invoked after a component is mounted. */
	// afterMount(component) { }

	/** Hook invoked after the DOM is updated with a component's latest render. */
	// afterUpdate(component) { }

	/** Hook invoked immediately before a component is unmounted. */
	// beforeUnmount(component) { }
};
<commit_msg>optimize recomputekey<commit_after>import style from './options/style';
import event from './options/event';

/*
 * @private
 * @description ensure each vnode has a stable and unique key
 */
function recomputeKey (children) {
	children = children || []
	var keyMap = {}, outerKey = 0, keyArr = [], varUndefined

	// set key and reset keyMap, keyArr
	function setKey() {
		keyArr.splice(0).forEach(function (info) {
	        info[2].key = info[0].key = '.' + outerKey + ':$' + String(info[1]).replace(/^\.[\S]+:\$/g, '');
	    });
		keyMap = {}
	}
	children.forEach(function(vnode) {
		var props = vnode && vnode.attributes || {},
			key = props.key,
			invalidKey = key === varUndefined && ++outerKey
		// duplicate key
		if (key in keyMap) ++outerKey && setKey()
		// vnode has no key
		if (invalidKey) {
			setKey()
		// props, origin key, vnode
		} else {
			keyArr.push([props, keyMap[key] = key, vnode])
		}
	})
	// last !! outerKey++ NOT ++outerKey
    outerKey++ && setKey();
}

/** Global options
 *	@public
 *	@namespace options {Object}
 */
export default {

	/** If `true`, `prop` changes trigger synchronous component updates.
	 *	@name syncComponentUpdates
	 *	@type Boolean
	 *	@default true
	 */
	//syncComponentUpdates: true,

	/** Processes all created VNodes.
	 *	@param {VNode} vnode	A newly-created VNode to normalize/process
	 */
	vnode(vnode) {
		// fork add to support react event sys
		vnode._hostParent = null;
		vnode._hostNode = null;
		vnode._rootNodeID = null;
		recomputeKey(vnode.children)
	},

	/** Hook for style process */
	style,

	/** Hook for event handle */
	_event: event

	/** Hook invoked after a component is mounted. */
	// afterMount(component) { }

	/** Hook invoked after the DOM is updated with a component's latest render. */
	// afterUpdate(component) { }

	/** Hook invoked immediately before a component is unmounted. */
	// beforeUnmount(component) { }
};
<|endoftext|>


<commit_before>(function($){
    // Restores select box state to saved value from localStorage.
    function restoreOptions() {
        $("#gitlabPath").val(config.getGitlabPath());
        $("#apiPath").val(config.getApiPath());
        $("#privateToken").val(config.getPrivateToken());
        $("#pollingSecond").val(config.getPollingSecond());
        $("#maxEventCount").val(config.getMaxEventCount());
        $("#newMarkMinute").val(config.getNewMarkMinute());
    }

    // Saves options to localStorage.
    function saveOptions(){
        var projects = {};
        $("#projects tr.project").each(function(){
            var projectId = parseInt($(this).attr("id"));

            var project = {name: $("#" + projectId + " td.name").text(), events: {}}
            $.each(gitlab.events(), function(index, event){
                project.events[event] = config.isChecked("#" + projectId + " td." + event + " input:checkbox");
            });
            projects[projectId] = project;
        });

        config.save({
            gitlabPath:    $("#gitlabPath").val(),
            apiPath:       $("#apiPath").val(),
            privateToken:  $("#privateToken").val(),
            pollingSecond: $("#pollingSecond").val(),
            maxEventCount: $("#maxEventCount").val(),
            newMarkMinute: $("#newMarkMinute").val(),
            projects:      projects
        });

        showStatus("Options Saved.");
    }

    function showStatus(status){
        $("span.status").text(status);
        setTimeout(function() {
            $("span.status").text("");
        }, 750);
    }

    function refreshProjects(){
        $("#projects").empty();

        if(config.getPrivateToken().length > 0){
            gitlab.getProjects(function(project){
                var tr = $("<tr/>").attr({id: project.id}).addClass("project");
                $("<td/>").addClass("name").text(project.path_with_namespace).appendTo(tr);

                var project = config.getProject(project.id);
                var events = gitlab.events();

                for(var i = 0; i < events.length; i++){
                    var event = events[i];
                    var checked = project.events[event] || false;

                    var label = $("<label/>").addClass("checkbox-inline");
                    $("<input/>").attr({type: "checkbox", checked: checked}).appendTo(label);
                    $("<span/>").text(event).appendTo(label);
                    $("<td/>").addClass(event).append(label).appendTo(tr);
                }

                tr.appendTo( $("#projects") );
            });
        }
    }

    function clearCache(){
        config.clearCache();
        showStatus("Cache cleared");
    }

    $(document).ready(function(){
        restoreOptions();
        refreshProjects();

        $("button.save").click(function(){
            saveOptions();
        });

        $("button.clear").click(function(){
            clearCache();
            refreshProjects();
        });

        $("#load_repository").click(function(){
            saveOptions();
            refreshProjects();
        });
    });
})(jQuery);
<commit_msg>add icon to option<commit_after>(function($){
    // Restores select box state to saved value from localStorage.
    function restoreOptions() {
        $("#gitlabPath").val(config.getGitlabPath());
        $("#apiPath").val(config.getApiPath());
        $("#privateToken").val(config.getPrivateToken());
        $("#pollingSecond").val(config.getPollingSecond());
        $("#maxEventCount").val(config.getMaxEventCount());
        $("#newMarkMinute").val(config.getNewMarkMinute());
    }

    // Saves options to localStorage.
    function saveOptions(){
        var projects = {};
        $("#projects tr.project").each(function(){
            var projectId = parseInt($(this).attr("id"));

            var project = {name: $("#" + projectId + " td.name").text(), events: {}}
            $.each(gitlab.events(), function(index, event){
                project.events[event] = config.isChecked("#" + projectId + " td." + event + " input:checkbox");
            });
            projects[projectId] = project;
        });

        config.save({
            gitlabPath:    $("#gitlabPath").val(),
            apiPath:       $("#apiPath").val(),
            privateToken:  $("#privateToken").val(),
            pollingSecond: $("#pollingSecond").val(),
            maxEventCount: $("#maxEventCount").val(),
            newMarkMinute: $("#newMarkMinute").val(),
            projects:      projects
        });

        showStatus("Options Saved.");
    }

    function showStatus(status){
        $("span.status").text(status);
        setTimeout(function() {
            $("span.status").text("");
        }, 750);
    }

    function refreshProjects(){
        $("#projects").empty();

        if(config.getPrivateToken().length > 0){
            gitlab.getProjects(function(project){
                var tr = $("<tr/>").attr({id: project.id}).addClass("project");
                $("<td/>").addClass("name").text(project.path_with_namespace).appendTo(tr);

                var project = config.getProject(project.id);
                var events = gitlab.events();

                for(var i = 0; i < events.length; i++){
                    var event = events[i];
                    var checked = project.events[event] || false;

                    var label = $("<label/>").addClass("checkbox-inline");
                    $("<input/>").attr({type: "checkbox", checked: checked}).appendTo(label);
                    util.createEventIcon(event).appendTo(label);
                    $("<span/>").text(event).appendTo(label);
                    $("<td/>").addClass(event).append(label).appendTo(tr);
                }

                tr.appendTo( $("#projects") );
            });
        }
    }

    function clearCache(){
        config.clearCache();
        showStatus("Cache cleared");
    }

    $(document).ready(function(){
        restoreOptions();
        refreshProjects();

        $("button.save").click(function(){
            saveOptions();
        });

        $("button.clear").click(function(){
            clearCache();
            refreshProjects();
        });

        $("#load_repository").click(function(){
            saveOptions();
            refreshProjects();
        });
    });
})(jQuery);
<|endoftext|>


<commit_before>import { isFunction, isString, styleObjToCss, hashToClassName } from './util';

/** Global options
 *	@public
 *	@namespace options {Object}
 */
export default {

	/** If `true`, `prop` changes trigger synchronous component updates.
	 *	@name syncComponentUpdates
	 *	@type Boolean
	 *	@default true
	 */
	//syncComponentUpdates: true,

	/** Processes all created VNodes.
	 *	@param {VNode} vnode	A newly-created VNode to normalize/process
	 */
	vnode(n) {
		let attrs = n.attributes;
		if (!attrs || isFunction(n.nodeName)) return;

		// normalize className to class.
		let p = attrs.className;
		if (p) {
			attrs['class'] = p;
			delete attrs.className;
		}

		if (attrs['class']) normalize(attrs, 'class', hashToClassName);
		if (attrs.style) normalize(attrs, 'style', styleObjToCss);
	}
};

function normalize(obj, prop, fn) {
	let v = obj[prop];
	if (v && !isString(v)) {
		obj[prop] = fn(v);
	}
}
<commit_msg>Fix (needs a test) for `null`/`undefined` value when using `className` compat alias. Fixes #181<commit_after>import { isFunction, isString, styleObjToCss, hashToClassName } from './util';

/** Global options
 *	@public
 *	@namespace options {Object}
 */
export default {

	/** If `true`, `prop` changes trigger synchronous component updates.
	 *	@name syncComponentUpdates
	 *	@type Boolean
	 *	@default true
	 */
	//syncComponentUpdates: true,

	/** Processes all created VNodes.
	 *	@param {VNode} vnode	A newly-created VNode to normalize/process
	 */
	vnode(n) {
		let attrs = n.attributes;
		if (!attrs || isFunction(n.nodeName)) return;

		// normalize className to class.
		if ('className' in attrs) {
			attrs['class'] = attrs.className;
			delete attrs.className;
		}

		if (attrs['class']) normalize(attrs, 'class', hashToClassName);
		if (attrs.style) normalize(attrs, 'style', styleObjToCss);
	}
};

function normalize(obj, prop, fn) {
	let v = obj[prop];
	if (v && !isString(v)) {
		obj[prop] = fn(v);
	}
}
<|endoftext|>


<commit_before>import browser from './util/browser'

/**
 * Throws an error based on the warning
 * If the last argument is a DOM node, it
 * coerces it to a string before throwing.
 * @returns {undefined}
 */
const throwError = function (...args) {
  const last = args[args.length - 1]
  if ( last.outerHTML ) {
    args[args.length - 1] = `Element: \n  ${last.outerHTML}`
  }

  const error = new Error(args.join(' '))
  error.element = last

  throw error
}

/**
 * Show a warning
 * @returns {undefined}
 */
const showWarning = function (...args) {
  if ( browser ) {
    console.warn(...args)
  } else {
    console.warn(args.join('\n  '))
  }
}

/**
 * Creates a reporter function based on deprecated options
 * @arg {object} opts - The options passed by the user
 * @returns {function} The reporter
 */
const mkReporter = function (opts) {
  const {
    doThrow        = false
  , warningPrefix  = ''
  } = opts

  return function (info) {
    const {
      msg
    , displayName
    , DOMNode
    , url
    , tagName
    , severity
    } = info

    // build warning
    const warning = [
      displayName || tagName
    , warningPrefix.concat(msg)
    , `See '${url}' for more info.`
    , DOMNode || tagName
    ]

    if ( doThrow || severity === 'error' ) {
      throwError(...warning)
    } else {
      showWarning(...warning)
    }
  }
}

/**
 * Generate a deprecation warning when a key is present
 * in the options object
 * @arg {object} opts - the options object under scrutiny
 * @arg {string} name - the name of the deprecated option
 * @arg {string} msg  - an optional reason for the deprecation
 * @returns {undefined}
 */
const deprecate = function (opts, name, msg = '') {
  if ( name in opts ) {
    console.warn(`react-a11y: the \`${name}\` options is deprecated. ${msg}`)
  }
}

/**
 * Make a certain option mandatory
 * @arg {object} opts - the options object under scrutiny
 * @arg {string} name - the name of the mandatory option
 * @arg {string} msg  - an optional reason
 * @returns {undefined}
 */
const mandatory = function (opts, name, msg = '') {
  if ( !(name in opts) ) {
    throw new Error(`react-a11y: the \`${name}\` option is mandatory. ${msg}`)
  }
}

// always resolve to true
const always = () => true

// deprecation message
const msg = 'Use the `reporter` option to change how warnings are displayed.'

/**
 * Normalize and validate the options that the user passed in.
 * @arg {object} opts - The opts the user passed in
 * @returns {object} the validated options
 */
export default function (...args) {

  // signature is a11y(React, opts) or a11y(React, ReactDOM, opts)
  // so destructure args based on number of args passed
  const [
    React
  , ReactDOM
  , opts
  ] = args.length === 2
    ? args[1].version === undefined
      ? [ args[0], null, args[1] || {} ]
      : [ args[0], args[1], {} ]
    : args

  if (!React || !React.createElement) {
    throw new Error('react-a11y: missing argument `React`')
  }

  // make sure ReactDOM is passed in in browser code
  if ( browser && !(ReactDOM && ReactDOM.version) ) {
    throw new Error('react-a11y: missing argument `ReactDOM`')
  }

  deprecate(opts, 'includeSrcNode', msg)
  deprecate(opts, 'throw',          msg)
  deprecate(opts, 'warningPrefix',  msg)

  const {
    reporter     = mkReporter(opts) // make a reporter based on options
  , filterFn     = always
  , plugins      = []
  , rules        = {}
  } = opts

  return {
    React
  , ReactDOM
  , filterFn
  , reporter
  , plugins
  , rules
  }
}

<commit_msg>do not show url message if no url is present<commit_after>import browser from './util/browser'

/**
 * Throws an error based on the warning
 * If the last argument is a DOM node, it
 * coerces it to a string before throwing.
 * @returns {undefined}
 */
const throwError = function (...args) {
  const last = args[args.length - 1]
  if ( last.outerHTML ) {
    args[args.length - 1] = `Element: \n  ${last.outerHTML}`
  }

  const error = new Error(args.join(' '))
  error.element = last

  throw error
}

/**
 * Show a warning
 * @returns {undefined}
 */
const showWarning = function (...args) {
  if ( browser ) {
    console.warn(...args)
  } else {
    console.warn(args.join('\n  '))
  }
}

/**
 * Creates a reporter function based on deprecated options
 * @arg {object} opts - The options passed by the user
 * @returns {function} The reporter
 */
const mkReporter = function (opts) {
  const {
    doThrow        = false
  , warningPrefix  = ''
  } = opts

  return function (info) {
    const {
      msg
    , displayName
    , DOMNode
    , url
    , tagName
    , severity
    } = info

    // build warning
    const warning = [
      displayName || tagName
    , warningPrefix.concat(msg)
    , ...(url ? [`See '${url}' for more info.`] : [])
    , DOMNode || tagName
    ]

    if ( doThrow || severity === 'error' ) {
      throwError(...warning)
    } else {
      showWarning(...warning)
    }
  }
}

/**
 * Generate a deprecation warning when a key is present
 * in the options object
 * @arg {object} opts - the options object under scrutiny
 * @arg {string} name - the name of the deprecated option
 * @arg {string} msg  - an optional reason for the deprecation
 * @returns {undefined}
 */
const deprecate = function (opts, name, msg = '') {
  if ( name in opts ) {
    console.warn(`react-a11y: the \`${name}\` options is deprecated. ${msg}`)
  }
}

/**
 * Make a certain option mandatory
 * @arg {object} opts - the options object under scrutiny
 * @arg {string} name - the name of the mandatory option
 * @arg {string} msg  - an optional reason
 * @returns {undefined}
 */
const mandatory = function (opts, name, msg = '') {
  if ( !(name in opts) ) {
    throw new Error(`react-a11y: the \`${name}\` option is mandatory. ${msg}`)
  }
}

// always resolve to true
const always = () => true

// deprecation message
const msg = 'Use the `reporter` option to change how warnings are displayed.'

/**
 * Normalize and validate the options that the user passed in.
 * @arg {object} opts - The opts the user passed in
 * @returns {object} the validated options
 */
export default function (...args) {

  // signature is a11y(React, opts) or a11y(React, ReactDOM, opts)
  // so destructure args based on number of args passed
  const [
    React
  , ReactDOM
  , opts
  ] = args.length === 2
    ? args[1].version === undefined
      ? [ args[0], null, args[1] || {} ]
      : [ args[0], args[1], {} ]
    : args

  if (!React || !React.createElement) {
    throw new Error('react-a11y: missing argument `React`')
  }

  // make sure ReactDOM is passed in in browser code
  if ( browser && !(ReactDOM && ReactDOM.version) ) {
    throw new Error('react-a11y: missing argument `ReactDOM`')
  }

  deprecate(opts, 'includeSrcNode', msg)
  deprecate(opts, 'throw',          msg)
  deprecate(opts, 'warningPrefix',  msg)

  const {
    reporter     = mkReporter(opts) // make a reporter based on options
  , filterFn     = always
  , plugins      = []
  , rules        = {}
  } = opts

  return {
    React
  , ReactDOM
  , filterFn
  , reporter
  , plugins
  , rules
  }
}

<|endoftext|>


<commit_before>import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  Image,
  Navigator,
  TouchableOpacity,
  TextInput,
  Picker,
} from 'react-native';

class Setting extends Component {
  constructor(props){
    super(props);
    this.state = {
      category: "",
      radius: "",
      newPlace: "",
      showCancel: false,
      showCancel2: false,
    }
  }

  // toggleCancel(){
  //   this.setState({
  //     showCancel: !this.state.showCancel,
  //   });
  //  }
  //
  // toggleCancel2(){
  //       this.setState({
  //         showCancel2: !this.state.showCancel2,
  //       });
  //   }
  //
  // appendCategoryPicker(){
  //   console.log("click")
  //   if (this.state.showCancel){
  //   return(
  //     <View>
  //       <Picker
  //         selectedValue={this.state.category}
  //         onValueChange={(category) => this.setState({category: category})}>
  //         <Picker.Item label="Popular" value="tourist_destination" />
  //         <Picker.Item label="Restaurant" value="restaurant" />
  //         <Picker.Item label="Aquarium" value="aquarium" />
  //         <Picker.Item label="Casino" value="casino" />
  //         <Picker.Item label="Museum" value="museum" />
  //         <Picker.Item label="Night Life" value="night_club" />
  //         <Picker.Item label="Park" value="park" />
  //         <Picker.Item label="Zoo" value="zoo" />
  //         <Picker.Item label="Attractions" value="place_of_interset" />
  //       </Picker>
  //     </View>)
  //   } else {
  //       return null
  //     }
  // }
  //
  // appendRadiusPicker(){
  //   console.log("click")
  //   if (this.state.showCancel2){
  //   return(
  //     <View><Picker
  //         selectedValue={this.state.radius}
  //         onValueChange={(radius) => this.setState({radius: radius})}>
  //         <Picker.Item label="1 miles" value="10" />
  //         <Picker.Item label="5 miles" value="30" />
  //         <Picker.Item label="10 miles" value="50" />
  //         <Picker.Item label="20 miles" value="20" />
  //         <Picker.Item label="30 miles" value="80" />
  //       </Picker>
  //     </View>
  //       )
  //   } else {
  //       return null
  //     }
  // }
  render(){
    return(
      <View>
        {/*<Text style={styles.text}>Category: {this.state.category}</Text>
          <TouchableOpacity
              onPress={() => this.toggleCancel()}
              style={styles.button}>
                <Text style={styles.buttonText}>Choose Category</Text>
          </TouchableOpacity>
          {this.appendCategoryPicker()}
        </View>
        <View>
          <TouchableOpacity
            onPress={() => this.toggleCancel2()}
            style={styles.button2}>
              <Text style={styles.buttonText}>Choose Radius</Text>
          </TouchableOpacity>
          {this.appendRadiusPicker()}
        </View>*/}
        <View>
        <Picker
          selectedValue={this.state.radius}
          onValueChange={(radius) => this.setState({radius: radius})}>
          <Picker.Item label="1 miles" value="10" />
          <Picker.Item label="5 miles" value="30" />
          <Picker.Item label="10 miles" value="50" />
          <Picker.Item label="20 miles" value="20" />
          <Picker.Item label="30 miles" value="80" />
        </Picker>
        </View>
        <Text style={styles.text}>Radius</Text>
      <View>
        <Picker
          selectedValue={this.state.category}
          onValueChange={(category) => this.setState({category: category})} mode="dialog">
          <Picker.Item label="Popular" value="tourist_destination" />
          <Picker.Item label="Restaurant" value="restaurant" />
          <Picker.Item label="Aquarium" value="aquarium" />
          <Picker.Item label="Casino" value="casino" />
          <Picker.Item label="Museum" value="museum" />
          <Picker.Item label="Night Life" value="night_club" />
          <Picker.Item label="Park" value="park" />
          <Picker.Item label="Zoo" value="zoo" />
          <Picker.Item label="Attractions" value="place_of_interset" />
        </Picker>
      </View>
      <Text style={styles.text}>Categories</Text>

      </View>
    )
  }
};

const styles = StyleSheet.create({
  text: {
    flex: 1,
    width: 385,
    height: 35,
    borderRadius: 8,
    paddingLeft: 150,
    justifyContent: 'center',
    alignSelf: 'center',
    fontSize: 20,
    backgroundColor: '#30ABBD'
  },
})

export default Setting;
<commit_msg>change label of custom search into text<commit_after>import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  Image,
  Navigator,
  TouchableOpacity,
  TextInput,
  Picker,
} from 'react-native';

class Setting extends Component {
  constructor(props){
    super(props);
    this.state = {
      category: "",
      radius: "",
      newPlace: "",
      showCancel: false,
      showCancel2: false,
    }
  }

  // toggleCancel(){
  //   this.setState({
  //     showCancel: !this.state.showCancel,
  //   });
  //  }
  //
  // toggleCancel2(){
  //       this.setState({
  //         showCancel2: !this.state.showCancel2,
  //       });
  //   }
  //
  // appendCategoryPicker(){
  //   console.log("click")
  //   if (this.state.showCancel){
  //   return(
  //     <View>
  //       <Picker
  //         selectedValue={this.state.category}
  //         onValueChange={(category) => this.setState({category: category})}>
  //         <Picker.Item label="Popular" value="tourist_destination" />
  //         <Picker.Item label="Restaurant" value="restaurant" />
  //         <Picker.Item label="Aquarium" value="aquarium" />
  //         <Picker.Item label="Casino" value="casino" />
  //         <Picker.Item label="Museum" value="museum" />
  //         <Picker.Item label="Night Life" value="night_club" />
  //         <Picker.Item label="Park" value="park" />
  //         <Picker.Item label="Zoo" value="zoo" />
  //         <Picker.Item label="Attractions" value="place_of_interset" />
  //       </Picker>
  //     </View>)
  //   } else {
  //       return null
  //     }
  // }
  //
  // appendRadiusPicker(){
  //   console.log("click")
  //   if (this.state.showCancel2){
  //   return(
  //     <View><Picker
  //         selectedValue={this.state.radius}
  //         onValueChange={(radius) => this.setState({radius: radius})}>
  //         <Picker.Item label="1 miles" value="10" />
  //         <Picker.Item label="5 miles" value="30" />
  //         <Picker.Item label="10 miles" value="50" />
  //         <Picker.Item label="20 miles" value="20" />
  //         <Picker.Item label="30 miles" value="80" />
  //       </Picker>
  //     </View>
  //       )
  //   } else {
  //       return null
  //     }
  // }
  render(){
    return(
      <View>
        {/*<Text style={styles.text}>Category: {this.state.category}</Text>
          <TouchableOpacity
              onPress={() => this.toggleCancel()}
              style={styles.button}>
                <Text style={styles.buttonText}>Choose Category</Text>
          </TouchableOpacity>
          {this.appendCategoryPicker()}
        </View>
        <View>
          <TouchableOpacity
            onPress={() => this.toggleCancel2()}
            style={styles.button2}>
              <Text style={styles.buttonText}>Choose Radius</Text>
          </TouchableOpacity>
          {this.appendRadiusPicker()}
        </View>*/}
        <View>
        <Picker
          selectedValue={this.state.radius}
          onValueChange={(radius) => this.setState({radius: radius})}>
          <Picker.Item label="1 miles" value="10" />
          <Picker.Item label="5 miles" value="30" />
          <Picker.Item label="10 miles" value="50" />
          <Picker.Item label="20 miles" value="20" />
          <Picker.Item label="30 miles" value="80" />
        </Picker>
        </View>
        <Text style={styles.text1}>Radius</Text>
        <Text style={styles.text2}>Categories</Text>
      <View>
        <Picker
          selectedValue={this.state.category}
          onValueChange={(category) => this.setState({category: category})} mode="dialog">
          <Picker.Item label="Popular" value="tourist_destination" />
          <Picker.Item label="Restaurant" value="restaurant" />
          <Picker.Item label="Aquarium" value="aquarium" />
          <Picker.Item label="Casino" value="casino" />
          <Picker.Item label="Museum" value="museum" />
          <Picker.Item label="Night Life" value="night_club" />
          <Picker.Item label="Park" value="park" />
          <Picker.Item label="Zoo" value="zoo" />
          <Picker.Item label="Attractions" value="place_of_interset" />
        </Picker>
      </View>

      </View>
    )
  }
};

const styles = StyleSheet.create({
  text1: {
    position: 'absolute',
    top: 98,
    left: 18,
    height: 38,
    fontSize: 23,
  },
  text2: {
    position: 'absolute',
    top: 312,
    left: 10,
    height: 38,
    fontSize: 23,
  },
})

export default Setting;
<|endoftext|>


<commit_before>define([
	'./Df_Core/js/array'
	, './Df_Core/js/string'
], function(array, string) {
	return {
		array: array
		, string: string
	};
});<commit_msg>no message<commit_after>define([
	'./Df_Core/js/array'
	, './Df_Core/js/string'
], function(array, string) {
	return {
		array: array
		/**
		 * 2016-04-20
		 * @param value
		 * @returns {Boolean}
		 */
		,defined: function(value) {return 'undefined' !== typeof value;}
		, string: string
		/**
		 * 2016-04-20
		 * @param value
		 * @returns {Boolean}
		 */
		,undefined: function(value) {return !this.defined(value);}
	};
});<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var calculateScore = function(guess, secret) {
    var cows = 0;
    var bulls = 0;

    for (var i=0;i<secret.length;++i) {
	var secretChar = secret[i];
	if (guess.indexOf(secretChar) !== -1)
	    cows++;
    }

    return { cows: cows, bulls: bulls };
};

describe('cows and bulls', function() {
    describe('scoring', function() {
	it('no matches scores zero cows and bulls', function() {
	    var score = calculateScore('1234','5678');

	    assert.equal(score.cows, 0);
	    assert.equal(score.bulls, 0);
	});

	it('single match in wrong place scores one cows', function() {
	    var score = calculateScore('1234', '5671');

	    assert.equal(score.cows, 1);
	    assert.equal(score.bulls, 0);
	});

	it('single match in right place scores one bull', function() {
	    var score = calculateScore('1234', '1567');

	    assert.equal(score.cows, 0);
	    assert.equal(score.bulls, 1);
	});
    });
});
<commit_msg>passing test, time to refactor<commit_after>"use strict";

var assert = require('assert');

var calculateScore = function(guess, secret) {
    var cows = 0;
    var bulls = 0;

    for (var i=0;i<secret.length;++i) {
	var secretChar = secret[i];
	var found = guess.indexOf(secretChar);
	if (found === i)
	    bulls++
	else if (found !== -1)
	    cows++;
    }

    return { cows: cows, bulls: bulls };
};

describe('cows and bulls', function() {
    describe('scoring', function() {
	it('no matches scores zero cows and bulls', function() {
	    var score = calculateScore('1234','5678');

	    assert.equal(score.cows, 0);
	    assert.equal(score.bulls, 0);
	});

	it('single match in wrong place scores one cows', function() {
	    var score = calculateScore('1234', '5671');

	    assert.equal(score.cows, 1);
	    assert.equal(score.bulls, 0);
	});

	it('single match in right place scores one bull', function() {
	    var score = calculateScore('1234', '1567');

	    assert.equal(score.cows, 0);
	    assert.equal(score.bulls, 1);
	});
    });
});
<|endoftext|>