<commit_before>var moment = require('moment');
var fromNow = v => moment(v).fromNow();

var segments = require('../segments/segments').choices;

export default function (nga, admin) {

    var customer = admin.getEntity('customers');
    customer.listView()
        .title('Visitors')
        .fields([
            nga.field('avatar', 'template')
                .label('')
                .template(entry => `<img src="${ entry.values.avatar }" width="25" style="margin-top:-5px" />`),
            nga.field('last_name', 'template') // use last_name for sorting
                .label('Name')
                .isDetailLink(true)
                .template('{{ entry.values.first_name }} {{ entry.values.last_name }}'),
            nga.field('last_seen', 'datetime')
                .map(fromNow)
                .cssClasses('hidden-xs'),
            nga.field('nb_commands', 'number')
                .label('Commands')
                .cssClasses(entry => entry && entry.values.nb_commands ? 'hidden-xs' : 'transparent hidden-xs'),
            nga.field('total_spent', 'amount')
                .cssClasses(entry => entry && entry.values.total_spent ? 'hidden-xs' : 'transparent hidden-xs'),
            nga.field('latest_purchase', 'datetime')
                .cssClasses('hidden-xs'),
            nga.field('has_newsletter', 'boolean')
                .label('Newsletter')
                .cssClasses('hidden-xs'),
            nga.field('segments', 'template')
                .template('{{ group }}')
                .cssClasses('hidden-xs'),
        ])
        .filters([
            nga.field('q', 'template')
                .label('')
                .pinned(true)
                .template('
<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>
'),
            nga.field('groups', 'choice')
                .label('Segment')
                .choices(segments),
            nga.field('last_seen_gte', 'datetime')
                .label('Visited since'),
            nga.field('has_ordered', 'boolean'),
            nga.field('has_newsletter', 'boolean'),
        ])
        .sortField('first_seen')
        .sortDir('DESC')
        .listActions(['edit'])
        .exportFields([
            nga.field('avatar', 'text'),
            nga.field('last_name', 'template').label('Name').template(entry => entry.values.first_name + ' ' + entry.values.last_name),
            nga.field('last_seen', 'datetime'),
            nga.field('nb_commands', 'number').label('Commands'),
            nga.field('total_spent', 'amount'),
            nga.field('latest_purchase', 'datetime'),
            nga.field('has_newsletter', 'boolean').label('Newsletter'),
            nga.field('segments', 'template').template(entry => `${entry.values.groups}`),
        ]);
    customer.editionView()
        .title(' {{ entry.values.first_name }} {{ entry.values.last_name }}\'s details')
        .fields([
            nga.field('first_name'),
            nga.field('last_name'),
            nga.field('email', 'email'),
            nga.field('address', 'text'),
            nga.field('zipcode'),
            nga.field('city'),
            nga.field('birthday', 'date'),
            nga.field('first_seen', 'datetime')
                .editable(false),
            nga.field('latest_purchase', 'datetime')
                .editable(false),
            nga.field('last_seen', 'datetime')
                .editable(false),
            nga.field('has_newsletter', 'boolean'),
            nga.field('groups', 'choices')
                .choices(segments),
            nga.field('commands', 'referenced_list')
                .label('Latest commands')
                .targetEntity(admin.getEntity('commands'))
                .targetReferenceField('customer_id')
                .targetFields([
                    nga.field('date')
                        .map(fromNow),
                    nga.field('reference')
                        .isDetailLink(true),
                    nga.field('nb_items')
                        .map((v,e) => e.basket.length),
                    nga.field('total', 'amount'),
                    nga.field('status')
                ])
                .listActions(['<ma-edit-button entry="::entry" entity="::entity" size="xs" label="Details"></ma-edit-button>'])
                .perPage(5)
                .sortField('date')
                .sortDir('DESC'),
            nga.field('commands button', 'template')
                .label('')
                .template('<ma-filtered-list-button entity-name="commands" filter="{ customer_id: entry.values.id }"></ma-filtered-list-button>'),
            nga.field('reviews', 'referenced_list')
                .label('Latest reviews')
                .targetEntity(admin.getEntity('reviews'))
                .targetReferenceField('customer_id')
                .targetFields([
                    nga.field('date')
                        .label('Posted')
                        .map(fromNow)
                        .isDetailLink(true),
                    nga.field('product_id', 'reference')
                        .label('Product')
                        .targetEntity(admin.getEntity('products'))
                        .targetField(nga.field('reference')),
                    nga.field('rating', 'template')
                        .template('<star-rating stars="{{ entry.values.rating }}"></star-rating>'),
                    nga.field('comment')
                        .map(function truncate(value) {
                            if (!value) {
                                return '';
                            }
                            return value.length > 50 ? value.substr(0, 50) + '...' : value;
                        }),
                    nga.field('status', 'choice')
                        .choices([
                            { label: 'accepted', value: 'accepted' },
                            { label: 'rejected', value: 'rejected' },
                            { label: 'pending', value: 'pending' }
                        ])
                        .cssClasses(function(entry) { // add custom CSS classes to inputs and columns
                            if (!entry) return;
                            if (entry.values.status == 'accepted') {
                                return 'text-center bg-success';
                            }
                            if (entry.values.status == 'rejected') {
                                return 'text-center bg-danger';
                            }
                            return 'text-center bg-warning';
                        }),
                ])
                .listActions(['<ma-edit-button entry="::entry" entity="::entity" size="xs" label="Details"></ma-edit-button>'])
                .perPage(5)
                .sortField('date')
                .sortDir('DESC'),
            nga.field('reviews button', 'template')
                .label('')
                .template('<ma-filtered-list-button entity-name="reviews" filter="{ customer_id: entry.values.id }"></ma-filtered-list-button>'),
        ])

    return customer;
}
<commit_msg>update config.js<commit_after>var moment = require('moment');
var fromNow = v => moment(v).fromNow();

var segments = require('../segments/segments').choices;

export default function (nga, admin) {

    var customer = admin.getEntity('customers');
    customer.listView()
        .title('Visitors')
        .fields([
            nga.field('avatar', 'template')
                .label('')
                .template(entry => `<img src="${ entry.values.avatar }" width="25" style="margin-top:-5px" />`),
            nga.field('last_name', 'template') // use last_name for sorting
                .label('Name')
                .isDetailLink(true)
                .template('{{ entry.values.first_name }} {{ entry.values.last_name }}'),
            nga.field('last_seen', 'datetime')
                .map(fromNow)
                .cssClasses('hidden-xs'),
            nga.field('nb_commands', 'template')
                .template('number')
                .label('Commands')
                .cssClasses(entry => entry && entry.values.nb_commands ? 'hidden-xs' : 'transparent hidden-xs'),
            nga.field('total_spent', 'amount')
                .cssClasses(entry => entry && entry.values.total_spent ? 'hidden-xs' : 'transparent hidden-xs'),
            nga.field('latest_purchase', 'datetime')
                .cssClasses('hidden-xs'),
            nga.field('has_newsletter', 'boolean')
                .label('Newsletter')
                .cssClasses('hidden-xs'),
            nga.field('segments', 'template')
                .template('{{ group }}')
                .cssClasses('hidden-xs'),
        ])
        .filters([
            nga.field('q', 'template')
                .label('')
                .pinned(true)
                .template('
<input type="text" ng-model="value" placeholder="Search" class="form-control"></input>
'),
            nga.field('groups', 'choice')
                .label('Segment')
                .choices(segments),
            nga.field('last_seen_gte', 'datetime')
                .label('Visited since'),
            nga.field('has_ordered', 'boolean'),
            nga.field('has_newsletter', 'boolean'),
        ])
        .sortField('first_seen')
        .sortDir('DESC')
        .listActions(['edit'])
        .exportFields([
            nga.field('avatar', 'text'),
            nga.field('last_name', 'template').label('Name').template(entry => entry.values.first_name + ' ' + entry.values.last_name),
            nga.field('last_seen', 'datetime'),
            nga.field('nb_commands', 'number').label('Commands'),
            nga.field('total_spent', 'amount'),
            nga.field('latest_purchase', 'datetime'),
            nga.field('has_newsletter', 'boolean').label('Newsletter'),
            nga.field('segments', 'template').template(entry => `${entry.values.groups}`),
        ]);
    customer.editionView()
        .title(' {{ entry.values.first_name }} {{ entry.values.last_name }}\'s details')
        .fields([
            nga.field('first_name'),
            nga.field('last_name'),
            nga.field('email', 'email'),
            nga.field('address', 'text'),
            nga.field('zipcode'),
            nga.field('city'),
            nga.field('birthday', 'date'),
            nga.field('first_seen', 'datetime')
                .editable(false),
            nga.field('latest_purchase', 'datetime')
                .editable(false),
            nga.field('last_seen', 'datetime')
                .editable(false),
            nga.field('has_newsletter', 'boolean'),
            nga.field('groups', 'choices')
                .choices(segments),
            nga.field('commands', 'referenced_list')
                .label('Latest commands')
                .targetEntity(admin.getEntity('commands'))
                .targetReferenceField('customer_id')
                .targetFields([
                    nga.field('date')
                        .map(fromNow),
                    nga.field('reference')
                        .isDetailLink(true),
                    nga.field('nb_items')
                        .map((v,e) => e.basket.length),
                    nga.field('total', 'amount'),
                    nga.field('status')
                ])
                .listActions(['<ma-edit-button entry="::entry" entity="::entity" size="xs" label="Details"></ma-edit-button>'])
                .perPage(5)
                .sortField('date')
                .sortDir('DESC'),
            nga.field('commands button', 'template')
                .label('')
                .template('<ma-filtered-list-button entity-name="commands" filter="{ customer_id: entry.values.id }"></ma-filtered-list-button>'),
            nga.field('reviews', 'referenced_list')
                .label('Latest reviews')
                .targetEntity(admin.getEntity('reviews'))
                .targetReferenceField('customer_id')
                .targetFields([
                    nga.field('date')
                        .label('Posted')
                        .map(fromNow)
                        .isDetailLink(true),
                    nga.field('product_id', 'reference')
                        .label('Product')
                        .targetEntity(admin.getEntity('products'))
                        .targetField(nga.field('reference')),
                    nga.field('rating', 'template')
                        .template('<star-rating stars="{{ entry.values.rating }}"></star-rating>'),
                    nga.field('comment')
                        .map(function truncate(value) {
                            if (!value) {
                                return '';
                            }
                            return value.length > 50 ? value.substr(0, 50) + '...' : value;
                        }),
                    nga.field('status', 'choice')
                        .choices([
                            { label: 'accepted', value: 'accepted' },
                            { label: 'rejected', value: 'rejected' },
                            { label: 'pending', value: 'pending' }
                        ])
                        .cssClasses(function(entry) { // add custom CSS classes to inputs and columns
                            if (!entry) return;
                            if (entry.values.status == 'accepted') {
                                return 'text-center bg-success';
                            }
                            if (entry.values.status == 'rejected') {
                                return 'text-center bg-danger';
                            }
                            return 'text-center bg-warning';
                        }),
                ])
                .listActions(['<ma-edit-button entry="::entry" entity="::entity" size="xs" label="Details"></ma-edit-button>'])
                .perPage(5)
                .sortField('date')
                .sortDir('DESC'),
            nga.field('reviews button', 'template')
                .label('')
                .template('<ma-filtered-list-button entity-name="reviews" filter="{ customer_id: entry.values.id }"></ma-filtered-list-button>'),
        ])

    return customer;
}
<|endoftext|>


<commit_before>/*
 A Finite State Machine,
 deterministic or non-deterministic.

 Usage:

    SM.alphabet = [String];

    SM.generateState(name, start, fin);
    .
    .
    .

    SM.generateEdge(fromState, toState, symbols);
    .
    .
    .

    SM.evalString(String);

    (see details below)

*/
var SM = {

    _states : [],
    _edges : [],

    // All routes that have to be checked
    // and all of their current States.
    _routes : [],
    _crntStates : [],

    // _routes.length === _routeIndexes.length

    // _routeIndexes stores the index of the input string that
    // SM has already evaluated up to.

    // inputStr[_routeIndexes[i]] has been evaluated
    // for _routes[i]
    _routeIndexes : [],

    alphabet : [],

    // Usage : SM._clearRoutes(str);
    // Post : _routes and _crntStates have been
    //        initialized for next evaluation of a String.
    _clearRoutes : function(str) {
        this._routes = [];
        this._crntStates = [];

        this._crntStates[0] = this.findState('q0');    // starting state
        this._routes[0] = str + ' |';

        this._routeIndexes[0] = 0;
    },

    // Usage : SM.generateState(name, start, fin);
    // Pre : name is a String. start and fin are Booleans.
    // Post : A new State with the name name and specifications
    //        whether it's a starting or a final State (or neither)
    //        has been added to _states.
    generateState : function(name, start, fin) {
        var state = new State({
            name : name,
            start : start,
            fin : fin
        });

        this._states.push(state);
    },

    // Usage : SM.generateEdge(fromState, toState, symbols)
    // Pre : fromState and toState are of type State. symbols
    //       is an array of Strings.
    // Post : A new Edge connecting fromState and toState
    //        by symbols.
    generateEdge : function(fromState, toState, symbols) {
        var edge = new Edge({
            fromState : fromState,
            toState : toState,
            symbols : symbols
        });

        this._edges.push(edge);

        fromState.outgoingEdges.push(edge);
        toState.incomingEdges.push(edge);
    },

    // Usage : SM.findState(name);
    // Pre : name is a String
    // Return value : A State with the name name,
    //                if such a State exists.
    findState : function(name) {
        for (var i in this._states) {
            if (this._states[i].name === name) {
                return this._states[i];
            }
        }
        console.log('No state with that name exists');
        return false;
    },

    // Usage : SM.evalString(str);
    // Return value : An array of routes the SM takes
    //                to evaluate the input str.
    evalString : function(str) {

        str = str.toLowerCase();    // upper case symbols accepted as well
        this._clearRoutes(str);  // make ready for evaluation

        for (var r = 0; r < this._routes.length; r++) {
            var routeIndex = this._routeIndexes[r];

            // str[0...routeIndex] has been evaluated
            // for SM._routes[r].
            for (var i = routeIndex; i < str.length; i++) {

                // Stop evaluating if one of the input symbols
                // is not in the alphabet.
                if (!this.symbolInAlphabet(str[i], r)) break;

                var crntState = this._crntStates[r];

                this._routes[r] += crntState.name + ' -> ';
                var psblEdges = crntState.psblTrans(str[i]);

                // If the SM is non-deterministic it has to
                // split up the routes and evaluate
                // every possible route.
                if (psblEdges.length > 1) {

                    var epsilonIndexes = [];

                    for (var j = 0; j < psblEdges.length; j++)
                        if (psblEdges[j].length === 2) {
                            epsilonIndexes.push(j);
                            psblEdges[j] = psblEdges[j][0];
                        }

                    //this._routeIndexes.push(routeIndex+1);
                    this.splitRoutes(psblEdges.length,
                                     this._routes[r],
                                     psblEdges);

                    for (var j = 0; j < psblEdges.length; j++) {
                        if (util.containsIndex(epsilonIndexes, j)) {
                            this._routeIndexes[j] = routeIndex;
                        }
                        else
                            this._routeIndexes[j] = routeIndex + 1;
                    }
                }
                else if (psblEdges.length === 1) {
                    if (psblEdges[0].length === 2) {
                        this._routeIndexes[r] = routeIndex;
                        this._crntStates[r] = psblEdges[0][0].toState;
                        --i;
                    }
                    else {
                        this._routeIndexes[r] = routeIndex + 1;
                        this._crntStates[r] = psblEdges[0].toState;
                    }
                }
                else
                    this._routes[r] += "No possible transition from " + crntState.name
                                        + " given input " + str[i];
            }
            this.addAcceptance(r);
        }
        this.printRoutes();
    },

    // Usage : SM.splitRoutes(nr, route, psblEdges);
    // Post : SM._routes now has nr entries of route.
    //        SM._crntStates[r]s now has nr entries of this._crntStates[r]
    splitRoutes : function(nr, route, psblEdges) {

        this._routes.pop();
        this._crntStates.pop();

        for (var i = 0; i < nr; i++) {
            this._routes.push(route);
            this._crntStates.push(psblEdges[i].toState);
        }
    },

    // Usage : SM.printRoutes();
    // Post : The routes SM took to evaluate the input
    //        have been logged to the console.
    printRoutes : function() {
        for (var i = 0; i < this._routes.length; i++) {
            console.log('ROUTE ' + (i+1) + ' ' + this._routes[i]);
        }
    },

    // Usage : SM.symbolInAlphabet(str, r)
    // Post : Error msg printed if false
    // Return value : true if str is in SM.alphabet
    symbolInAlphabet : function(str, r) {
        if (util.containsStr(this.alphabet, str)) {
            return true;
        }
        else {
            this._routes[r] += ' Input Error: ' + str + ' not in alphabet of SM';
            return false;
        }
    },

    // Usage : SM.addAcceptance(r);
    // Pre : SM has evaluated all of it's input.
    //       r is the index for the current route and
    //       the current State in the evaluation.
    // Post : An indication of whether the input str
    //        has been accepted or not by SM has been
    //        pushed to the back of the route string.
    addAcceptance : function(r) {
        if (this._crntStates[r].name === 'qf') {
             this._routes[r] += ' ' + this._crntStates[r].name + ' : Accepted';
        }
        else{
            this._routes[r] += ' ' + this._crntStates[r].name + ' : NOT Accepted';
        }
    }

};

// DFA
// L = (a(a + b)) + (b(a + b))
/*
SM.alphabet = ['a', 'b'];

SM.generateState('q0', true, false);
SM.generateState('q1', false, false);
SM.generateState('q2', false, false);
SM.generateState('qf', false, true);

SM.generateEdge(SM.findState('q0'), SM.findState('q1'), ['a']);
SM.generateEdge(SM.findState('q0'), SM.findState('q2'), ['b']);
SM.generateEdge(SM.findState('q1'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q2'), SM.findState('qf'), ['a', 'b']);

// Trash state
SM.generateState('qTrash', false, false);
SM.generateEdge(SM.findState('qf'), SM.findState('qTrash'), ['a', 'b']);
SM.generateEdge(SM.findState('qTrash'), SM.findState('qTrash'), ['a', 'b']);

// Accepted
SM.evalString('aa');
SM.evalString('ab');
SM.evalString('ba');
SM.evalString('bb');
SM.evalString('AB');

// NOT Accepted
SM.evalString('aaa');
SM.evalString('bab');
SM.evalString('aaaaaaa');

// Wrong input
SM.evalString('ac');
*/

/*
Output from DFA above.

aa |q0 -> q1 ->  qf : Accepted
ab |q0 -> q1 ->  qf : Accepted
ba |q0 -> q2 ->  qf : Accepted
bb |q0 -> q2 ->  qf : Accepted
ab |q0 -> q1 ->  qf : Accepted
aaa |q0 -> q1 -> qf ->  qTrash : NOT Accepted
bab |q0 -> q2 -> qf ->  qTrash : NOT Accepted
aaaaaaa |q0 -> q1 -> qf -> qTrash -> qTrash -> qTrash -> qTrash ->  qTrash : NOT Accepted
ac |q0 ->  Input Error: c not in alphabet of SM q1 : NOT Accepted
*/

// NFA
// L = (a(a + b)) + (b(a + b))

// Three possible ways for (a(a + b))

SM.alphabet = ['a', 'b'];

SM.generateState('q0', true, false);
SM.generateState('q1', false, false);
SM.generateState('q2', false, false);
SM.generateState('q3', false, false);
SM.generateState('q4', false, false);
SM.generateState('qf', false, true);

SM.generateEdge(SM.findState('q0'), SM.findState('q1'), ['a']);
SM.generateEdge(SM.findState('q0'), SM.findState('q2'), ['b']);
SM.generateEdge(SM.findState('q0'), SM.findState('q3'), ['a']);
SM.generateEdge(SM.findState('q0'), SM.findState('q4'), ['a']);
SM.generateEdge(SM.findState('q1'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q2'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q3'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q4'), SM.findState('qf'), ['a']);

SM.generateState('qEps', false, false);
SM.generateEdge(SM.findState('q0'), SM.findState('qEps'), [consts.EPSILON]);
SM.generateEdge(SM.findState('qEps'), SM.findState('q1'), [consts.EPSILON]);

SM.evalString('aa');

/*
Output from NFA above

ROUTE 1 aa |q0 -> q1 ->  qf : Accepted
ROUTE 2 aa |q0 -> q3 ->  qf : Accepted
ROUTE 3 aa |q0 -> q4 ->  qf : Accepted
*/<commit_msg>epsilon edge evaluation<commit_after>/*
 A Finite State Machine,
 deterministic or non-deterministic.

 Usage:

    SM.alphabet = [String];

    SM.generateState(name, start, fin);
    .
    .
    .

    SM.generateEdge(fromState, toState, symbols);
    .
    .
    .

    SM.evalString(String);

    (see details below)

*/
var SM = {

    _states : [],
    _edges : [],

    // All routes that have to be checked
    // and all of their current States.
    _routes : [],
    _crntStates : [],

    // _routes.length === _routeIndexes.length

    // _routeIndexes stores the index of the input string that
    // SM has already evaluated up to.

    // inputStr[_routeIndexes[i]] has been evaluated
    // for _routes[i]
    _routeIndexes : [],

    alphabet : [],

    // Usage : SM._clearRoutes(str);
    // Post : _routes and _crntStates have been
    //        initialized for next evaluation of a String.
    _clearRoutes : function(str) {
        this._routes = [];
        this._crntStates = [];

        this._crntStates[0] = this.findState('q0');    // starting state
        this._routes[0] = str + ' |';

        this._routeIndexes[0] = 0;
    },

    // Usage : SM.generateState(name, start, fin);
    // Pre : name is a String. start and fin are Booleans.
    // Post : A new State with the name name and specifications
    //        whether it's a starting or a final State (or neither)
    //        has been added to _states.
    generateState : function(name, start, fin) {
        var state = new State({
            name : name,
            start : start,
            fin : fin
        });

        this._states.push(state);
    },

    // Usage : SM.generateEdge(fromState, toState, symbols)
    // Pre : fromState and toState are of type State. symbols
    //       is an array of Strings.
    // Post : A new Edge connecting fromState and toState
    //        by symbols.
    generateEdge : function(fromState, toState, symbols) {
        var edge = new Edge({
            fromState : fromState,
            toState : toState,
            symbols : symbols
        });

        this._edges.push(edge);

        fromState.outgoingEdges.push(edge);
        toState.incomingEdges.push(edge);
    },

    // Usage : SM.findState(name);
    // Pre : name is a String
    // Return value : A State with the name name,
    //                if such a State exists.
    findState : function(name) {
        for (var i in this._states) {
            if (this._states[i].name === name) {
                return this._states[i];
            }
        }
        console.log('No state with that name exists');
        return false;
    },

    // Usage : SM.evalString(str);
    // Return value : An array of routes the SM takes
    //                to evaluate the input str.
    evalString : function(str) {

        str = str.toLowerCase();    // upper case symbols accepted as well
        this._clearRoutes(str);  // make ready for evaluation

        for (var r = 0; r < this._routes.length; r++) {
            var routeIndex = this._routeIndexes[r];

            // str[0...routeIndex] has been evaluated
            // for SM._routes[r].
            for (var i = routeIndex; i < str.length; i++) {

                // Stop evaluating if one of the input symbols
                // is not in the alphabet.
                if (!this.symbolInAlphabet(str[i], r)) break;

                var crntState = this._crntStates[r];

                this._routes[r] += crntState.name + ' -> ';
                var psblEdges = crntState.psblTrans(str[i]);

                // If the SM is non-deterministic it has to
                // split up the routes and evaluate
                // every possible route.
                if (psblEdges.length > 1) {

                    var epsilonIndexes = [];

                    for (var j = 0; j < psblEdges.length; j++)
                        if (psblEdges[j].length === 2) {
                            epsilonIndexes.push(j);
                            psblEdges[j] = psblEdges[j][0];
                        }

                    //this._routeIndexes.push(routeIndex+1);
                    this.splitRoutes(psblEdges.length,
                                     this._routes[r],
                                     psblEdges);

                    for (var j = 0; j < psblEdges.length; j++) {
                        if (util.contains(epsilonIndexes, j)) {
                            this._routeIndexes[j] = routeIndex;
                        }
                        else
                            this._routeIndexes[j] = routeIndex + 1;
                    }
                }
                else if (psblEdges.length === 1) {
                    if (psblEdges[0].length === 2) {
                        this._routeIndexes[r] = routeIndex;
                        this._crntStates[r] = psblEdges[0][0].toState;
                        --i;
                    }
                    else {
                        this._routeIndexes[r] = routeIndex + 1;
                        this._crntStates[r] = psblEdges[0].toState;
                    }
                }
                else
                    this._routes[r] += "No possible transition from " + crntState.name
                                        + " given input " + str[i] + ', ';
            }
            this.addAcceptance(r);
        }
        this.printRoutes();
    },

    // Usage : SM.splitRoutes(nr, route, psblEdges);
    // Post : SM._routes now has nr entries of route.
    //        SM._crntStates[r]s now has nr entries of this._crntStates[r]
    splitRoutes : function(nr, route, psblEdges) {

        this._routes.pop();
        this._crntStates.pop();

        for (var i = 0; i < nr; i++) {
            this._routes.push(route);
            this._crntStates.push(psblEdges[i].toState);
        }
    },

    // Usage : SM.printRoutes();
    // Post : The routes SM took to evaluate the input
    //        have been logged to the console.
    printRoutes : function() {
        for (var i = 0; i < this._routes.length; i++) {
            console.log('ROUTE ' + (i+1) + ' ' + this._routes[i]);
        }
    },

    // Usage : SM.symbolInAlphabet(str, r)
    // Post : Error msg printed if false
    // Return value : true if str is in SM.alphabet
    symbolInAlphabet : function(str, r) {
        if (util.contains(this.alphabet, str)) {
            return true;
        }
        else {
            this._routes[r] += ' Input Error: ' + str + ' not in alphabet of SM';
            return false;
        }
    },

    // Usage : SM.addAcceptance(r);
    // Pre : SM has evaluated all of it's input.
    //       r is the index for the current route and
    //       the current State in the evaluation.
    // Post : An indication of whether the input str
    //        has been accepted or not by SM has been
    //        pushed to the back of the route string.
    addAcceptance : function(r) {
        if (this._crntStates[r].name === 'qf') {
             this._routes[r] += ' ' + this._crntStates[r].name + ' : Accepted';
        }
        else{
            this._routes[r] += ' ' + this._crntStates[r].name + ' : NOT Accepted';
        }
    }

};

// DFA
// L = (a(a + b)) + (b(a + b))
/*
SM.alphabet = ['a', 'b'];

SM.generateState('q0', true, false);
SM.generateState('q1', false, false);
SM.generateState('q2', false, false);
SM.generateState('qf', false, true);

SM.generateEdge(SM.findState('q0'), SM.findState('q1'), ['a']);
SM.generateEdge(SM.findState('q0'), SM.findState('q2'), ['b']);
SM.generateEdge(SM.findState('q1'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q2'), SM.findState('qf'), ['a', 'b']);

// Trash state
SM.generateState('qTrash', false, false);
SM.generateEdge(SM.findState('qf'), SM.findState('qTrash'), ['a', 'b']);
SM.generateEdge(SM.findState('qTrash'), SM.findState('qTrash'), ['a', 'b']);

// Accepted
SM.evalString('aa');
SM.evalString('ab');
SM.evalString('ba');
SM.evalString('bb');
SM.evalString('AB');

// NOT Accepted
SM.evalString('aaa');
SM.evalString('bab');
SM.evalString('aaaaaaa');

// Wrong input
SM.evalString('ac');
*/

/*
Output from DFA above.

aa |q0 -> q1 ->  qf : Accepted
ab |q0 -> q1 ->  qf : Accepted
ba |q0 -> q2 ->  qf : Accepted
bb |q0 -> q2 ->  qf : Accepted
ab |q0 -> q1 ->  qf : Accepted
aaa |q0 -> q1 -> qf ->  qTrash : NOT Accepted
bab |q0 -> q2 -> qf ->  qTrash : NOT Accepted
aaaaaaa |q0 -> q1 -> qf -> qTrash -> qTrash -> qTrash -> qTrash ->  qTrash : NOT Accepted
ac |q0 ->  Input Error: c not in alphabet of SM q1 : NOT Accepted
*/

// NFA
// L = (a(a + b)) + (b(a + b))

// Three possible ways for (a(a + b))

SM.alphabet = ['a', 'b'];

SM.generateState('q0', true, false);
SM.generateState('q1', false, false);
SM.generateState('q2', false, false);
SM.generateState('q3', false, false);
SM.generateState('q4', false, false);
SM.generateState('qf', false, true);

SM.generateEdge(SM.findState('q0'), SM.findState('q1'), ['a']);
SM.generateEdge(SM.findState('q0'), SM.findState('q2'), ['b']);
SM.generateEdge(SM.findState('q0'), SM.findState('q3'), ['a']);
SM.generateEdge(SM.findState('q0'), SM.findState('q4'), ['a']);
SM.generateEdge(SM.findState('q1'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q2'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q3'), SM.findState('qf'), ['a', 'b']);
SM.generateEdge(SM.findState('q4'), SM.findState('qf'), ['a']);

SM.generateState('qEps', false, false);
SM.generateEdge(SM.findState('q0'), SM.findState('qEps'), [consts.EPSILON]);
SM.generateEdge(SM.findState('qEps'), SM.findState('q1'), [consts.EPSILON]);

SM.evalString('aa');

/*
Output from NFA above

ROUTE 1 aa |q0 -> q1 ->  qf : Accepted
ROUTE 2 aa |q0 -> q3 ->  qf : Accepted
ROUTE 3 aa |q0 -> q4 ->  qf : Accepted
ROUTE 4 aa |q0 -> qEps -> q1 -> qf -> No possible transition from qf given input a,  qf : Accepted
*/
<|endoftext|>


<commit_before>// Copyright 2002-2014, University of Colorado

/**
 * A unit that is drawable with a specific renderer
 *
 * APIs for drawable types:
 *
 * DOM: {
 *   domElement: {HTMLElement}
 * }
 *
 * OHTWO TODO: add more API information, and update
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var inherit = require( 'PHET_CORE/inherit' );
  var scenery = require( 'SCENERY/scenery' );

  scenery.Drawable = function Drawable( renderer ) {
    this.initializeDrawable( renderer );
  };
  var Drawable = scenery.Drawable;

  inherit( Object, Drawable, {
    initializeDrawable: function( renderer ) {
      this.cleanDrawable();

      this.renderer = renderer;

      this.dirty = true;

      return this;
    },

    cleanDrawable: function() {
      // what drawble we are being rendered (or put) into (will be filled in later)
      this.parentDrawable = null;

      // linked list handling (will be filled in later)
      this.previousDrawable = null;
      this.nextDrawable = null;

      // similar but pending handling, so that we can traverse both orders at the same time for stitching
      this.pendingPreviousDrawable = null;
      this.pendingNextDrawable = null;
    },

    markDirty: function() {
      if ( !this.dirty ) {
        this.dirty = true;

        // TODO: notify what we want to call repaint() later
        if ( this.parentDrawable ) {
          this.parentDrawable.markDirtyInstance( this );
        }
      }
    },

    dispose: function() {
      this.cleanDrawable();
    }
  } );

  return Drawable;
} );
<commit_msg>Adding unique global IDs for drawables<commit_after>// Copyright 2002-2014, University of Colorado

/**
 * A unit that is drawable with a specific renderer
 *
 * APIs for drawable types:
 *
 * DOM: {
 *   domElement: {HTMLElement}
 * }
 *
 * OHTWO TODO: add more API information, and update
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var inherit = require( 'PHET_CORE/inherit' );
  var scenery = require( 'SCENERY/scenery' );

  var globalId = 1;

  scenery.Drawable = function Drawable( renderer ) {
    this.initializeDrawable( renderer );
  };
  var Drawable = scenery.Drawable;

  inherit( Object, Drawable, {
    initializeDrawable: function( renderer ) {
      // unique ID for drawables
      this.id = this.id || globalId++;

      this.cleanDrawable();

      this.renderer = renderer;

      this.dirty = true;

      return this;
    },

    cleanDrawable: function() {
      // what drawble we are being rendered (or put) into (will be filled in later)
      this.parentDrawable = null;

      // linked list handling (will be filled in later)
      this.previousDrawable = null;
      this.nextDrawable = null;

      // similar but pending handling, so that we can traverse both orders at the same time for stitching
      this.pendingPreviousDrawable = null;
      this.pendingNextDrawable = null;
    },

    markDirty: function() {
      if ( !this.dirty ) {
        this.dirty = true;

        // TODO: notify what we want to call repaint() later
        if ( this.parentDrawable ) {
          this.parentDrawable.markDirtyInstance( this );
        }
      }
    },

    dispose: function() {
      this.cleanDrawable();
    }
  } );

  return Drawable;
} );
<|endoftext|>


<commit_before>/* @flow */
import generatePrefixMap from './generatePrefixMap'
import generatePluginList from './generatePluginList'

function generateImportString(plugin, compatibility) {
  if (compatibility) {
    return `var ${plugin} = require('inline-style-prefixer/plugins/${plugin}')`
  }
  return `import ${plugin} from 'inline-style-prefixer/plugins/${plugin}'`
}

function generateFile(prefixMap, pluginList, compatibility) {
  const pluginImports = pluginList
    .map(plugin => generateImportString(plugin, compatibility))
    .join('\n')

  const moduleExporter = compatibility ? 'module.exports = ' : 'export default'
  const pluginExport = `[${pluginList.join(',')}]`
  const prefixMapExport = JSON.stringify(prefixMap)

  const prefixVariables = [
    'var w = ["Webkit"];',
    'var m = ["Moz"];',
    'var ms = ["ms"];',
    'var wm = ["Webkit","Moz"];',
    'var wms = ["Webkit","ms"];',
    'var wmms = ["Webkit","Moz","ms"];',
  ].join('\n')

  return `${pluginImports}
${prefixVariables}

${moduleExporter} {
  plugins: ${pluginExport},
  prefixMap: ${prefixMapExport
    .replace(/\["Webkit"\]/g, 'w')
    .replace(/\["Moz"\]/g, 'm')
    .replace(/\["ms"\]/g, 'ms')
    .replace(/\["Webkit","Moz"\]/g, 'wm')
    .replace(/\["Webkit","ms"\]/g, 'wms')
    .replace(/\["Webkit","Moz","ms"\]/g, 'wmms')}
}`
}

function saveFile(fileContent: string, path: string): void {
  /* eslint-disable global-require */
  const fs = require('fs')
  /* eslint-enable global-require */

  fs.writeFile(path, fileContent, err => {
    if (err) {
      throw err
    }

    console.log(`Successfully saved data to "${path}".`)
  })
}

type GenerationOption = {
  path?: string,
  compatibility?: boolean,
  plugins?: boolean,
  prefixMap?: boolean,
}

const defaultOptions = {
  prefixMap: true,
  plugins: true,
  compatibility: false,
}

export default function generateData(
  browserList: Object,
  userOptions: GenerationOption = {}
) {
  const options = {
    ...defaultOptions,
    ...userOptions,
  }

  const { compatibility, plugins, path, prefixMap } = options

  const requiredPrefixMap = prefixMap ? generatePrefixMap(browserList) : {}
  const requiredPlugins = plugins ? generatePluginList(browserList) : []

  if (path) {
    saveFile(
      generateFile(requiredPrefixMap, requiredPlugins, compatibility),
      path
    )
  }

  return {
    prefixMap: requiredPrefixMap,
    plugins: requiredPlugins,
  }
}
<commit_msg>Generator writes correct plugin paths<commit_after>/* @flow */
import generatePrefixMap from './generatePrefixMap'
import generatePluginList from './generatePluginList'

function generateImportString(plugin, compatibility) {
  if (compatibility) {
    return `var ${plugin} = require('inline-style-prefixer/lib/plugins/${plugin}')`
  }
  return `import ${plugin} from 'inline-style-prefixer/lib/plugins/${plugin}'`
}

function generateFile(prefixMap, pluginList, compatibility) {
  const pluginImports = pluginList
    .map(plugin => generateImportString(plugin, compatibility))
    .join('\n')

  const moduleExporter = compatibility ? 'module.exports = ' : 'export default'
  const pluginExport = `[${pluginList.join(',')}]`
  const prefixMapExport = JSON.stringify(prefixMap)

  const prefixVariables = [
    'var w = ["Webkit"];',
    'var m = ["Moz"];',
    'var ms = ["ms"];',
    'var wm = ["Webkit","Moz"];',
    'var wms = ["Webkit","ms"];',
    'var wmms = ["Webkit","Moz","ms"];',
  ].join('\n')

  return `${pluginImports}
${prefixVariables}

${moduleExporter} {
  plugins: ${pluginExport},
  prefixMap: ${prefixMapExport
    .replace(/\["Webkit"\]/g, 'w')
    .replace(/\["Moz"\]/g, 'm')
    .replace(/\["ms"\]/g, 'ms')
    .replace(/\["Webkit","Moz"\]/g, 'wm')
    .replace(/\["Webkit","ms"\]/g, 'wms')
    .replace(/\["Webkit","Moz","ms"\]/g, 'wmms')}
}`
}

function saveFile(fileContent: string, path: string): void {
  /* eslint-disable global-require */
  const fs = require('fs')
  /* eslint-enable global-require */

  fs.writeFile(path, fileContent, err => {
    if (err) {
      throw err
    }

    console.log(`Successfully saved data to "${path}".`)
  })
}

type GenerationOption = {
  path?: string,
  compatibility?: boolean,
  plugins?: boolean,
  prefixMap?: boolean,
}

const defaultOptions = {
  prefixMap: true,
  plugins: true,
  compatibility: false,
}

export default function generateData(
  browserList: Object,
  userOptions: GenerationOption = {}
) {
  const options = {
    ...defaultOptions,
    ...userOptions,
  }

  const { compatibility, plugins, path, prefixMap } = options

  const requiredPrefixMap = prefixMap ? generatePrefixMap(browserList) : {}
  const requiredPlugins = plugins ? generatePluginList(browserList) : []

  if (path) {
    saveFile(
      generateFile(requiredPrefixMap, requiredPlugins, compatibility),
      path
    )
  }

  return {
    prefixMap: requiredPrefixMap,
    plugins: requiredPlugins,
  }
}
<|endoftext|>


<commit_before>
// Monitors forms and fields with .validateable for input, and generates
// and manages .error-labels following those that fail validation.
//
// Messages are read from:
//
// 1. A validation attribute on the input:
//    <input type="email" data-validation-type="That is not an email address" />
//    The attribute name can be modified globally by setting
//    config.attributePrefix. The postfix word is always one of 'pattern',
//    'max', 'min', 'step', 'maxlength', 'type' or 'required'.
//
// 2. The messages in config.messages.
//
// 3. The browser's default validation message (which is available on the
//    input at the point that it fails validation).
//
// Inputs inside or with .validateable are given .validated after they are
// first validated, enabling pre- as well as post- validation styles.

import { get, invoke, Stream } from '../../fn/module.js';
import { create, events, matches, next, remove, validate, isValid, classes, after } from '../module.js';

var isValidateable = matches('.validateable, .validateable input, .validateable textarea, .validateable select, [validateable], [validateable] input, [validateable] textarea, [validateable] select');

var types = {
	patternMismatch: 'pattern',
	rangeOverflow:   'max',
	rangeUnderflow:  'min',
	stepMismatch:    'step',
	tooLong:         'maxlength',
	typeMismatch:    'type',
	valueMissing:    'required'
};

export const config = {
	errorLabelClass: 'error-label',

	// Class added to validated nodes (note: not valid nodes, necessarily,
	// but nodes that have been validated at least once).
	validatedClass:  'validated',

	// Prefix for input attributes containing validation messages.
	attributePrefix: 'data-validation-',

	// Global object for validation messages.
	messages: {
		// pattern:
		// max:
		// min:
		// step:
		// maxlength:
		// type:
		// required:
	}
};

function negate(fn) {
	return function() {
		return !fn.apply(this, arguments);
	};
}

function isShowingMessage(node) {
	return node.nextElementSibling
		&& matches('.' + config.errorLabelClass, node.nextElementSibling);
}

function toError(node) {
	var validity = node.validity;
	var prefix   = config.attributePrefix;
	var messages = config.messages;
	var name;

	for (name in validity) {
		if (name !== 'valid' && validity[name]) {
			return {
				type: name,
				attr: types[name],
				name: node.name,
				text: (prefix && node.getAttribute(prefix + types[name]))
					|| (messages && messages[types[name]])
					|| node.validationMessage,
				node: node
			};
		}
	}
}

function renderError(error) {
	var input = error.node;
	var node  = input;

	// Find the last error
	while (node.nextElementSibling && matches('.' + config.errorLabelClass, node.nextElementSibling)) {
		node = node.nextElementSibling;
	}

	var label = create('label', {
		textContent: error.text,
		for:         input.id,
		class:       config.errorLabelClass
	});

	after(node, label);

	if (error.type === 'customError') {
		node.setCustomValidity(error.text);

		events('input', node)
		.take(1)
		.each(function() {
			node.setCustomValidity('');
		});
	}
}

function addValidatedClass(input) {
	classes(input).add(config.validatedClass);
}

function removeMessages(input) {
	var node = input;

	while ((node = next(node)) && matches('.' + config.errorLabelClass, node)) {
		remove(node);
	}
}

events('input dom-update', document)
.map(get('target'))
.filter(isValidateable)
// This came from somewhere - is it for nullifying custom messages? Todo: review.
.tap(invoke('setCustomValidity', ['']))
.filter(isValid)
.each(removeMessages);

events('focusout', document)
.map(get('target'))
.filter(isValidateable)
.each(validate);

events('submit', document)
.map(get('target'))
.filter(isValidateable)
.each(addValidatedClass);

// Add event in capture phase
document.addEventListener(
	'invalid',

	// Push to stream
	Stream.of()
	.map(get('target'))
	.filter(isValidateable)
	.tap(addValidatedClass)
	.filter(negate(isShowingMessage))
	.map(toError)
	.each(renderError)
	.push,

	// Capture phase
	true
);
<commit_msg>Allows multiple classnames in errorLabelClass<commit_after>
// Monitors forms and fields with .validateable for input, and generates
// and manages .error-labels following those that fail validation.
//
// Messages are read from:
//
// 1. A validation attribute on the input:
//    <input type="email" data-validation-type="That is not an email address" />
//    The attribute name can be modified globally by setting
//    config.attributePrefix. The postfix word is always one of 'pattern',
//    'max', 'min', 'step', 'maxlength', 'type' or 'required'.
//
// 2. The messages in config.messages.
//
// 3. The browser's default validation message (which is available on the
//    input at the point that it fails validation).
//
// Inputs inside or with .validateable are given .validated after they are
// first validated, enabling pre- as well as post- validation styles.

import { get, invoke, Stream } from '../../fn/module.js';
import { create, events, matches, next, remove, validate, isValid, classes, after } from '../module.js';

var isValidateable = matches('.validateable, .validateable input, .validateable textarea, .validateable select, [validateable], [validateable] input, [validateable] textarea, [validateable] select');

var types = {
	patternMismatch: 'pattern',
	rangeOverflow:   'max',
	rangeUnderflow:  'min',
	stepMismatch:    'step',
	tooLong:         'maxlength',
	typeMismatch:    'type',
	valueMissing:    'required'
};

export const config = {
	errorLabelClass: 'error-label',

	// Class added to validated nodes (note: not valid nodes, necessarily,
	// but nodes that have been validated at least once).
	validatedClass:  'validated',

	// Prefix for input attributes containing validation messages.
	attributePrefix: 'data-validation-',

	// Global object for validation messages.
	messages: {
		// pattern:
		// max:
		// min:
		// step:
		// maxlength:
		// type:
		// required:
	}
};

function negate(fn) {
	return function() {
		return !fn.apply(this, arguments);
	};
}

function isShowingMessage(node) {
	return node.nextElementSibling
		&& matches('.' + config.errorLabelClass.trim().split(/\s+/).join('.'), node.nextElementSibling);
}

function toError(node) {
	var validity = node.validity;
	var prefix   = config.attributePrefix;
	var messages = config.messages;
	var name;

	for (name in validity) {
		if (name !== 'valid' && validity[name]) {
			return {
				type: name,
				attr: types[name],
				name: node.name,
				text: (prefix && node.getAttribute(prefix + types[name]))
					|| (messages && messages[types[name]])
					|| node.validationMessage,
				node: node
			};
		}
	}
}

function renderError(error) {
	var input = error.node;
	var node  = input;

	// Find the last error
	while (node.nextElementSibling && matches('.' + config.errorLabelClass.trim().split(/\s+/).join('.'), node.nextElementSibling)) {
		node = node.nextElementSibling;
	}

	var label = create('label', {
		textContent: error.text,
		for:         input.id,
		class:       config.errorLabelClass
	});

	after(node, label);

	if (error.type === 'customError') {
		node.setCustomValidity(error.text);

		events('input', node)
		.take(1)
		.each(function() {
			node.setCustomValidity('');
		});
	}
}

function addValidatedClass(input) {
	classes(input).add(config.validatedClass);
}

function removeMessages(input) {
	var node = input;

	while ((node = next(node)) && matches('.' + config.errorLabelClass.trim().split(/\s+/).join('.'), node)) {
		remove(node);
	}
}

events('input dom-update', document)
.map(get('target'))
.filter(isValidateable)
// This came from somewhere - is it for nullifying custom messages? Todo: review.
.tap(invoke('setCustomValidity', ['']))
.filter(isValid)
.each(removeMessages);

events('focusout', document)
.map(get('target'))
.filter(isValidateable)
.each(validate);

events('submit', document)
.map(get('target'))
.filter(isValidateable)
.each(addValidatedClass);

// Add event in capture phase
document.addEventListener(
	'invalid',

	// Push to stream
	Stream.of()
	.map(get('target'))
	.filter(isValidateable)
	.tap(addValidatedClass)
	.filter(negate(isShowingMessage))
	.map(toError)
	.each(renderError)
	.push,

	// Capture phase
	true
);
<|endoftext|>


<commit_before>// FeedDailyTable class
// Variables needed: window.apikey_read, window.emoncms_path
// Libraries needed: jQuery, bootstrap-datetimepicker
//
// Parameters:
//   divId: id of the table container
//   startDateId: id of the start date input
//   endDateId: id of the end date input
//   feeds: array of feed objects
//     id: id of the feed
//     name: name of the feed
//   localization (optional): localization object
//     day: string for 'day'
//     nodata: string for no data on the table ('No data available at the selected date range')
//     exportcsv: string for the export to csv button ('Export to CSV') ("" to not show the button)
//     total: string for last row ('Total') ("" to not show the total)
function FeedDailyTable(divId, startDateId, endDateId, feeds, localization) {
  "use strict";

  // Default localization options
  var defaultLocalization = {
    day: "Day",
    nodata: "No data available at the selected date range",
    exportcsv: "Export to CSV",
    total: "Total"
  };

  // Parameter properties
  // Container id
  this.divId = divId;
  // Id of the start date of the daily table
  this.startDateId = startDateId;
  // Id of the end date of the daily table
  this.endDateId = endDateId;
  // Array of feeds with id and name
  this.feeds = feeds;
  // Localization
  this.localization = $.extend({}, defaultLocalization, localization);

  // Object properties
  // Container object
  this.div = $(divId);
  // Feed/col names
  this.feed_ids = [];
  this.col_names = [];
  for(var index in this.feeds) {
    this.feed_ids[this.feeds[index].name] = "f" + this.feeds[index].id;
    this.col_names[index] = this.feeds[index].name;
  }
  // Data array (["d" + date] => [["f" + feedid] => value])
  this.feedData = [];

  // Save context
  var self = this;

  // Build the table
  // Table container
  this.table = $("
", {class: "daily-table"});
  this.div.append(this.table);
  if(this.localization.exportcsv != "") {
    // Export to CSV button
    this.button = $("<input/>", {
      class: "export-table-csv btn",
      name: "exportcsv",
      value: self.localization.exportcsv
    });
    this.div.append(this.button);
    this.button.click(function() {
      self.exportCSV();
    });
  }

  // Sets the events
  $(document).ready(function () {
    // Build empty table
    self.buildTable();
    // On date change
    $(self.startDateId + ", " + self.endDateId).on("changeDate", function() {
      self.reloadTable();
    });
    // Initial data
    self.reloadTable();
  });

  // Build the initial table from the data
  this.buildTable = function() {
    var emptyTable = "

" + this.localization.day + "	";
    for(var index in this.col_names) {
      emptyTable += "" + this.col_names[index] + "	";
    }
    emptyTable += "

" + this.localization.nodata + "

";
    this.table.append($(emptyTable));
  };

  // Reload data on date change
  this.reloadTable = function() {
    var startDate = this.getTimestamp($(this.startDateId + " :input").val(), false);
    var endDate = this.getTimestamp($(this.endDateId + " :input").val(), true);
    if((startDate === false) || (endDate === false)) {
      $(startDateId).addClass("error");
      $(endDateId).addClass("error");
      return;
    }
    var now = +new Date();
    if(startDate >= endDate) {
      $(this.startDateId + ", " + this.endDateId).addClass("error");
      return;
    }
    if((startDate >= now) || (endDate >= now)) {
      $(this.startDateId + ", " + this.endDateId).addClass("error");
      return;
    }
    $(this.startDateId + ", " + this.endDateId).removeClass("error");
    this.getData(startDate, endDate);
  };

  // Refresh the table's data
  this.getData = function(startDate, endDate) {
    // New data array (["d" + date] => [["f" + feedid] => value])
    var tmpData = [];
    // Requests array
    var requests = [];
    // Declare feed here so if the for loop has only 1 iteration we keep the feed index
    var feed;
    for(var index in this.feeds) {
      feed = this.feeds[index].id;
      requests.push(this.getFeedData(feed, startDate, endDate));
    }
    // Now
    var beginningDayNow = this.getBeginningOfDay(new Date());

    // Save context before jQuery calls
    var self = this;
    // When all requests finish
    $.when.apply($, requests).done(function() {
      // Special case if there is only one request
      if (requests.length == 1) {
        $.map(arguments[0], function(feedData) {
          // Normalize date
          feedData[0] = self.getBeginningOfDay(feedData[0]);
          // Between dates (and do not include today's data)
          if((feedData[0] >= (startDate + 60 * 1000)) && (feedData[0] < (endDate - 60 * 1000)) && (feedData[0] <= beginningDayNow)) {
            if(tmpData["d" + feedData[0]] == undefined) {
              tmpData["d" + feedData[0]] = [];
            }
            tmpData["d" + feedData[0]]["f" + feed] = feedData[1];
          }
        });
      }
      // For each request
      else {
        var index = 0;
        $.each(arguments, function(index, responseData) {
          $.map(responseData[0], function(feedData) {
            // Normalize date
            feedData[0] = self.getBeginningOfDay(feedData[0]);
            var feed = self.feeds[index].id;
            // Between dates (and do not include today's data)
            if((feedData[0] >= (startDate + 60 * 1000)) && (feedData[0] < (endDate - 60 * 1000)) && (feedData[0] <= beginningDayNow)) {
              if(tmpData["d" + feedData[0]] == undefined) {
                tmpData["d" + feedData[0]] = [];
              }
              tmpData["d" + feedData[0]]["f" + feed] = feedData[1];
            }
          });
          index++;
        });
      }
      // Substitute data
      self.feedData = tmpData;
      // Replace table data
      self.replaceTableData();
    });
  };

  // Get the beggining of the day of a date
  this.getBeginningOfDay = function(date) {
    var newDate = new Date(date);
    newDate.setHours(0);
    newDate.setMinutes(0);
    newDate.setSeconds(0);
    newDate.setMilliseconds(0);
    return newDate.getTime();
  }

  // Get feed data (async)
  this.getFeedData = function(id, start, end) {
    // 1 Day interval
    var interval = 60 * 60 * 24;
    // 2 days more of margin (will discard later on the data outside the selected range)
    start -= 60 * 60 * 24 * 1000;
    end += 60 * 60 * 24 * 1000;
    return $.ajax({
      url: window.emoncms_path + "/feed/data.json?apikey=" + window.apikey_read,
      data: "id="+id+"&start="+start+"&end="+end+"&interval="+interval+"&skipmissing=0&limitinterval=0",
      dataType: "json"
    });
  };

  // Insert the new data into the table and replace the old data
  this.replaceTableData = function() {
    var tbody = this.table.find("tbody");
    var nodatarow = "
" + this.localization.nodata + "
";
    var tbodyHTML = "";
    tbody.empty();
    var numRows = 0;
    if(!$.isEmptyObject(this.feedData)) {
      // Data array (["d" + date] => [["f" + feedid] => value])
      for(var index in this.feedData) {
        var rowHTML = this.buildRow(parseInt(index.substring(1)), this.feedData[index]);
        if(rowHTML != "") {
          numRows++;
        }
        tbodyHTML += rowHTML;
      }
    }

    // No data check
    if(numRows == 0) {
      this.feedData = [];
      if(this.localization.nodata != "") {
        this.button.hide();
      }
      tbodyHTML += nodatarow;
    }

    // Show csv button
    if((this.localization.nodata != "") && (numRows != 0)) {
      this.button.show();
    }

    // Last row (total)
    var total = this.getTotalRow(this.feedData);
    var display = ((numRows > 1) && (this.localization.total != ""));
    var totalRow;
    if(display) {
      totalRow = "
" + this.localization.total + "	";
    } else {
      totalRow = "
" + this.localization.total + "	";
    }
    var divIdNoHash = this.divId.substring(1);
    var triggerChanges = "";
    for(var index in total) {
      var totalData = Math.round(parseFloat(total[index]) * 100) / 100;
      totalRow += "" + totalData + "	";
      triggerChanges += this.divId + "_total_" + index + ",";
    }
    totalRow += "
";
    tbodyHTML += totalRow;

    // Append data
    tbody.html(tbodyHTML);

    // Trigger changes
    $(triggerChanges.substring(0, triggerChanges.length - 1)).trigger("change");
  };

  // Builds an html row
  this.buildRow = function(date, rowData) {
    // Flags
    var anydata = false;
    // Row string (html)
    var row = "";
    for(var index in this.col_names) {
      var col = this.col_names[index];
      var feedid = this.feed_ids[col];
      var colData = rowData[feedid] == null ? 0 : rowData[feedid];
      colData = Math.round(colData * 100) / 100;
      row += "" + colData + "	";
      // Change flag
      if(rowData[feedid] != null) {
        anydata = true;
      }
    }
    // No data check
    if(anydata == false) {
      return "";
    }
    // Return row
    return "
" + (new Date(date)).toLocaleDateString() + "	" + row + "
";
  };

  // Translates a datetimepicker input to UNIX timestamp with appropiate offset
  this.getTimestamp = function (string, endDate) {
    var dateArray = string.split("/");
    if (dateArray.length != 3) {
      return false;
    }
    var date = (+new Date(dateArray[2], dateArray[1] - 1, dateArray[0]));
    // If date is today, and it is the end date, set it to now
    if(endDate) {
      // End of the day plus one minute
      date += 60 * 60 * 24 * 1000 + 60 * 1000;
    } else {
      // Start of the day minus one minute
      date -= 60 * 1000;
    }
    return date;
  };

  // Exports the table to csv
  this.exportCSV = function() {
    var self = this;
    if($.isEmptyObject(this.feedData)) {
      alert(self.localization.nodata);
      return;
    }
    // CSV header
    var csvheader = this.localization.day + "," + this.col_names.join(",") + "\r\n";

    // Data array (["d" + date] => [["f" + feedid] => value])
    var csvstring = "";
    var numRows = 0;
    var maxDate = 0;
    var minDate = +new Date();
    for(var index in this.feedData) {
      var date = parseInt(index.substring(1));
      if(date > maxDate) {
        maxDate = date;
      }
      if(date < minDate) {
        minDate = date;
      }
      var rowCSV = this.getCSVRow(date, this.feedData[index]);
      if(rowCSV != "") {
        numRows++;
      }
      csvstring += rowCSV;
    }
    // No data check
    if(numRows == 0) {
      alert(this.localization.nodata);
      return;
    }
    if((numRows > 1) && (this.localization.total != "")) {
      // Last row
      csvstring += this.getTotalRowCSV(this.feedData);
    }

    // Download data
    this.downloadCSV((new Date(minDate)).toLocaleDateString() + "---" + (new Date(maxDate)).toLocaleDateString(), csvheader + csvstring);
  };

  // Get a csv row
  this.getCSVRow = function(date, rowData) {
    // Flag
    var anydata = false;
    // Row array (columns)
    var row = [];
    for(var index in this.col_names) {
      var col = this.col_names[index];
      var feedid = this.feed_ids[col];
      var colData = rowData[feedid] == null ? 0 : rowData[feedid];
      row.push(colData);
      // Change flag
      if(rowData[feedid] != null) {
        anydata = true;
      }
    }
    // No data check
    if(!anydata) {
      return "";
    }

    // Return row
    return (new Date(date)).toLocaleDateString() + "," + row.join(",") + "\r\n";
  };

  // Gets the total row in csv
  this.getTotalRowCSV = function(feedData) {
    var row = this.getTotalRow(feedData);
    var rowCSV = "";
    for(var index in row) {
      rowCSV += row[index] + ",";
    }
    rowCSV = rowCSV.substring(0, rowCSV.length - 1);
    return this.localization.total + "," + rowCSV + "\r\n";
  }

  // Gets the total row
  this.getTotalRow = function(feedData) {
    var total = [];
    if(feedData.length == 0) {
      for(var index in this.feeds) {
        var feedid = this.feeds[index].id;
        total["f" + feedid] = 0;
      }
      return total;
    }
    for(var date in feedData) {
      for(var index in this.feeds) {
        var feedid = this.feeds[index].id;
        if(total["f" + feedid] === undefined) {
          total["f" + feedid] = 0;
        }
        if(feedData[date]["f" + feedid] != null) {
          total["f" + feedid] += parseFloat(feedData[date]["f" + feedid]);
        }
      }
    }
    return total;
  }

  // Download a csv
  this.downloadCSV = function(name, data) {
    // Create a temporary link and trigger its click
    var D = document;
    var a = D.createElement("a");
    a.href = 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data);
    a.setAttribute("download", name + ".csv");
    D.body.appendChild(a);
    setTimeout(function() {
      a.click();
      D.body.removeChild(a);
    }, 100);
  };
};
<commit_msg>Bugfix<commit_after>// FeedDailyTable class
// Variables needed: window.apikey_read, window.emoncms_path
// Libraries needed: jQuery, bootstrap-datetimepicker
//
// Parameters:
//   divId: id of the table container
//   startDateId: id of the start date input
//   endDateId: id of the end date input
//   feeds: array of feed objects
//     id: id of the feed
//     name: name of the feed
//   localization (optional): localization object
//     day: string for 'day'
//     nodata: string for no data on the table ('No data available at the selected date range')
//     exportcsv: string for the export to csv button ('Export to CSV') ("" to not show the button)
//     total: string for last row ('Total') ("" to not show the total)
function FeedDailyTable(divId, startDateId, endDateId, feeds, localization) {
  "use strict";

  // Default localization options
  var defaultLocalization = {
    day: "Day",
    nodata: "No data available at the selected date range",
    exportcsv: "Export to CSV",
    total: "Total"
  };

  // Parameter properties
  // Container id
  this.divId = divId;
  // Id of the start date of the daily table
  this.startDateId = startDateId;
  // Id of the end date of the daily table
  this.endDateId = endDateId;
  // Array of feeds with id and name
  this.feeds = feeds;
  // Localization
  this.localization = $.extend({}, defaultLocalization, localization);

  // Object properties
  // Container object
  this.div = $(divId);
  // Feed/col names
  this.feed_ids = [];
  this.col_names = [];
  for(var index in this.feeds) {
    this.feed_ids[this.feeds[index].name] = "f" + this.feeds[index].id;
    this.col_names[index] = this.feeds[index].name;
  }
  // Data array (["d" + date] => [["f" + feedid] => value])
  this.feedData = [];

  // Save context
  var self = this;

  // Build the table
  // Table container
  this.table = $("
", {class: "daily-table"});
  this.div.append(this.table);
  if(this.localization.exportcsv != "") {
    // Export to CSV button
    this.button = $("<input/>", {
      class: "export-table-csv btn",
      name: "exportcsv",
      value: self.localization.exportcsv
    });
    this.div.append(this.button);
    this.button.click(function() {
      self.exportCSV();
    });
  }

  // Sets the events
  $(document).ready(function () {
    // Build empty table
    self.buildTable();
    // On date change
    $(self.startDateId + ", " + self.endDateId).on("changeDate", function() {
      self.reloadTable();
    });
    // Initial data
    self.reloadTable();
  });

  // Build the initial table from the data
  this.buildTable = function() {
    var emptyTable = "

" + this.localization.day + "	";
    for(var index in this.col_names) {
      emptyTable += "" + this.col_names[index] + "	";
    }
    emptyTable += "

" + this.localization.nodata + "

";
    this.table.append($(emptyTable));
  };

  // Reload data on date change
  this.reloadTable = function() {
    var startDate = this.getTimestamp($(this.startDateId + " :input").val(), false);
    var endDate = this.getTimestamp($(this.endDateId + " :input").val(), true);
    if((startDate === false) || (endDate === false)) {
      $(startDateId).addClass("error");
      $(endDateId).addClass("error");
      return;
    }
    var now = +new Date();
    if(startDate >= endDate) {
      $(this.startDateId + ", " + this.endDateId).addClass("error");
      return;
    }
    if((startDate >= now) || (endDate >= now)) {
      $(this.startDateId + ", " + this.endDateId).addClass("error");
      return;
    }
    $(this.startDateId + ", " + this.endDateId).removeClass("error");
    this.getData(startDate, endDate);
  };

  // Refresh the table's data
  this.getData = function(startDate, endDate) {
    // New data array (["d" + date] => [["f" + feedid] => value])
    var tmpData = [];
    // Requests array
    var requests = [];
    // Declare feed here so if the for loop has only 1 iteration we keep the feed index
    var feed;
    for(var index in this.feeds) {
      feed = this.feeds[index].id;
      requests.push(this.getFeedData(feed, startDate, endDate));
    }
    // Now
    var beginningDayNow = this.getBeginningOfDay(new Date());

    // Save context before jQuery calls
    var self = this;
    // When all requests finish
    $.when.apply($, requests).done(function() {
      // Special case if there is only one request
      if (requests.length == 1) {
        $.map(arguments[0], function(feedData) {
          // Normalize date
          feedData[0] = self.getBeginningOfDay(feedData[0]);
          // Between dates (and do not include today's data)
          if((feedData[0] >= (startDate + 60 * 1000)) && (feedData[0] < (endDate - 60 * 1000)) && (feedData[0] <= beginningDayNow)) {
            if(tmpData["d" + feedData[0]] == undefined) {
              tmpData["d" + feedData[0]] = [];
            }
            tmpData["d" + feedData[0]]["f" + feed] = feedData[1];
          }
        });
      }
      // For each request
      else {
        var index = 0;
        $.each(arguments, function(index, responseData) {
          $.map(responseData[0], function(feedData) {
            // Normalize date
            feedData[0] = self.getBeginningOfDay(feedData[0]);
            var feed = self.feeds[index].id;
            // Between dates (and do not include today's data)
            if((feedData[0] >= (startDate + 60 * 1000)) && (feedData[0] < (endDate - 60 * 1000)) && (feedData[0] <= beginningDayNow)) {
              if(tmpData["d" + feedData[0]] == undefined) {
                tmpData["d" + feedData[0]] = [];
              }
              tmpData["d" + feedData[0]]["f" + feed] = feedData[1];
            }
          });
          index++;
        });
      }
      // Substitute data
      self.feedData = tmpData;
      // Replace table data
      self.replaceTableData();
    });
  };

  // Get the beggining of the day of a date
  this.getBeginningOfDay = function(date) {
    var newDate = new Date(date);
    newDate.setHours(0);
    newDate.setMinutes(0);
    newDate.setSeconds(0);
    newDate.setMilliseconds(0);
    return newDate.getTime();
  }

  // Get feed data (async)
  this.getFeedData = function(id, start, end) {
    // 1 Day interval
    var interval = 60 * 60 * 24;
    // 2 days more of margin (will discard later on the data outside the selected range)
    start -= 60 * 60 * 24 * 1000;
    end += 60 * 60 * 24 * 1000;
    return $.ajax({
      url: window.emoncms_path + "/feed/data.json?apikey=" + window.apikey_read,
      data: "id="+id+"&start="+start+"&end="+end+"&interval="+interval+"&skipmissing=0&limitinterval=0",
      dataType: "json"
    });
  };

  // Insert the new data into the table and replace the old data
  this.replaceTableData = function() {
    var tbody = this.table.find("tbody");
    var nodatarow = "
" + this.localization.nodata + "
";
    var tbodyHTML = "";
    tbody.empty();
    var numRows = 0;
    if(!$.isEmptyObject(this.feedData)) {
      // Data array (["d" + date] => [["f" + feedid] => value])
      for(var index in this.feedData) {
        var rowHTML = this.buildRow(parseInt(index.substring(1)), this.feedData[index]);
        if(rowHTML != "") {
          numRows++;
        }
        tbodyHTML += rowHTML;
      }
    }

    // No data check
    if(numRows == 0) {
      this.feedData = [];
      if(this.localization.nodata != "") {
        this.button.hide();
      }
      tbodyHTML += nodatarow;
    }

    // Show csv button
    if((this.localization.nodata != "") && (numRows != 0)) {
      this.button.show();
    }

    // Last row (total)
    var total = this.getTotalRow(this.feedData);
    var display = ((numRows > 1) && (this.localization.total != ""));
    var totalRow;
    if(display) {
      totalRow = "
" + this.localization.total + "	";
    } else {
      totalRow = "
" + this.localization.total + "	";
    }
    var divIdNoHash = this.divId.substring(1);
    var triggerChanges = "";
    for(var index in total) {
      var totalData = Math.round(parseFloat(total[index]) * 100) / 100;
      totalRow += "" + totalData + "	";
      triggerChanges += this.divId + "_total_" + index + ",";
    }
    totalRow += "
";
    tbodyHTML += totalRow;

    // Append data
    tbody.html(tbodyHTML);

    // Trigger changes
    $(triggerChanges.substring(0, triggerChanges.length - 1)).trigger("change");
  };

  // Builds an html row
  this.buildRow = function(date, rowData) {
    // Flags
    var anydata = false;
    // Row string (html)
    var row = "";
    for(var index in this.col_names) {
      var col = this.col_names[index];
      var feedid = this.feed_ids[col];
      var colData = rowData[feedid] == null ? 0 : rowData[feedid];
      colData = Math.round(colData * 100) / 100;
      row += "" + colData + "	";
      // Change flag
      if(rowData[feedid] != null) {
        anydata = true;
      }
    }
    // No data check
    if(anydata == false) {
      return "";
    }
    // Return row
    return "
" + (new Date(date)).toLocaleDateString() + "	" + row + "
";
  };

  // Translates a datetimepicker input to UNIX timestamp with appropiate offset
  this.getTimestamp = function (string, endDate) {
    var dateArray = string.split("/");
    if (dateArray.length != 3) {
      return false;
    }
    var date = (+new Date(dateArray[2], dateArray[1] - 1, dateArray[0]));
    // If date is today, and it is the end date, set it to now
    if(endDate) {
      // End of the day plus one minute
      date += 60 * 60 * 24 * 1000 + 60 * 1000;
    } else {
      // Start of the day minus one minute
      date -= 60 * 1000;
    }
    return date;
  };

  // Exports the table to csv
  this.exportCSV = function() {
    var self = this;
    if($.isEmptyObject(this.feedData)) {
      alert(self.localization.nodata);
      return;
    }
    // CSV header
    var csvheader = this.localization.day + "," + this.col_names.join(",") + "\r\n";

    // Data array (["d" + date] => [["f" + feedid] => value])
    var csvstring = "";
    var numRows = 0;
    var maxDate = 0;
    var minDate = +new Date();
    for(var index in this.feedData) {
      var date = parseInt(index.substring(1));
      if(date > maxDate) {
        maxDate = date;
      }
      if(date < minDate) {
        minDate = date;
      }
      var rowCSV = this.getCSVRow(date, this.feedData[index]);
      if(rowCSV != "") {
        numRows++;
      }
      csvstring += rowCSV;
    }
    // No data check
    if(numRows == 0) {
      alert(this.localization.nodata);
      return;
    }
    if((numRows > 1) && (this.localization.total != "")) {
      // Last row
      csvstring += this.getTotalRowCSV(this.feedData);
    }

    // Download data
    this.downloadCSV((new Date(minDate)).toLocaleDateString() + "---" + (new Date(maxDate)).toLocaleDateString(), csvheader + csvstring);
  };

  // Get a csv row
  this.getCSVRow = function(date, rowData) {
    // Flag
    var anydata = false;
    // Row array (columns)
    var row = [];
    for(var index in this.col_names) {
      var col = this.col_names[index];
      var feedid = this.feed_ids[col];
      var colData = rowData[feedid] == null ? 0 : rowData[feedid];
      row.push(colData);
      // Change flag
      if(rowData[feedid] != null) {
        anydata = true;
      }
    }
    // No data check
    if(!anydata) {
      return "";
    }

    // Return row
    return (new Date(date)).toLocaleDateString() + "," + row.join(",") + "\r\n";
  };

  // Gets the total row in csv
  this.getTotalRowCSV = function(feedData) {
    var row = this.getTotalRow(feedData);
    var rowCSV = "";
    for(var index in row) {
      rowCSV += row[index] + ",";
    }
    rowCSV = rowCSV.substring(0, rowCSV.length - 1);
    return this.localization.total + "," + rowCSV + "\r\n";
  }

  // Gets the total row
  this.getTotalRow = function(feedData) {
    var total = [];
    if(Object.keys(feedData).length == 0) {
      for(var index in this.feeds) {
        var feedid = this.feeds[index].id;
        total["f" + feedid] = 0;
      }
      return total;
    }
    for(var date in feedData) {
      for(var index in this.feeds) {
        var feedid = this.feeds[index].id;
        if(total["f" + feedid] === undefined) {
          total["f" + feedid] = 0;
        }
        if(feedData[date]["f" + feedid] != null) {
          total["f" + feedid] += parseFloat(feedData[date]["f" + feedid]);
        }
      }
    }
    return total;
  }

  // Download a csv
  this.downloadCSV = function(name, data) {
    // Create a temporary link and trigger its click
    var D = document;
    var a = D.createElement("a");
    a.href = 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data);
    a.setAttribute("download", name + ".csv");
    D.body.appendChild(a);
    setTimeout(function() {
      a.click();
      D.body.removeChild(a);
    }, 100);
  };
};
<|endoftext|>


<commit_before>function initMap() {
  var myLatLng = {lat: 43.891008, lng: -0.500929};

  // Create a map object and specify the DOM element for display.
  var map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  scrollwheel: false,
  zoom: 8,
  zoomControl: false,
  streetViewControl: false,
  mapTypeControl: false,
  scaleControl: false,
  draggable:false,
  disableDoubleClickZoom:true
  });

  //just a commit test another one

  // Create a marker and set its position.
  var marker = new google.maps.Marker({
  map: map,
  animation: google.maps.Animation.DROP,
  draggable: false,
  position: myLatLng,
  title: 'Hello World!'
  });
}
<commit_msg>Come on it s all correct<commit_after>function initMap() {
  var myLatLng = {lat: 43.891008, lng: -0.500929};

  // Create a map object and specify the DOM element for display.
  var map = new google.maps.Map(document.getElementById('map'), {
  center: myLatLng,
  scrollwheel: false,
  zoom: 8,
  zoomControl: false,
  streetViewControl: false,
  mapTypeControl: false,
  scaleControl: false,
  draggable:false,
  disableDoubleClickZoom:true
  });

  //just a commit test another one last test then i kill myself

  // Create a marker and set its position.
  var marker = new google.maps.Marker({
  map: map,
  animation: google.maps.Animation.DROP,
  draggable: false,
  position: myLatLng,
  title: 'Hello World!'
  });
}
<|endoftext|>


<commit_before>var utility = require('./utility');
var isSignificant = utility.isSignificant;
var reduceMatch = utility.reduceMatch;
var constants = require('./constants.json');
var aggregator = require('./aggregator');
var async = require('async');
var db = require('./db');
var r = require('./redis');
var redis = r.client;
var zlib = require('zlib');
var computeMatchData = require('./compute').computeMatchData;
module.exports = function updatePlayerCaches(match, options, cb) {
    //insert the match into db, then based on the existing document determine whether to do aggregations
    db.matches.findAndModify({
        match_id: match.match_id
    }, {
        $set: match
    }, {
        //don't upsert if inserting skill data
        upsert: options.type !== "skill",
        //explicitly declare we want the pre-modification document
        "new": false
    }, function(err, doc) {
        if (err) {
            return cb(err);
        }
        if (options.type === "skill" && !doc) {
            //we didn't add skill data, return immediately
            return cb(err);
        }
        async.each(match.players || options.players, function(p, cb) {
                redis.get("player:" + p.account_id, function(err, result) {
                    //if player cache doesn't exist, skip
                    var cache = result && !err ? JSON.parse(zlib.inflateSync(new Buffer(result, 'base64'))) : null;
                    if (cache) {
                        var match_copy = JSON.parse(JSON.stringify(match));
                        if (options.type !== "skill") {
                            //m.players[0] should be this player
                            //m.all_players should be all players
                            //duplicate this data into a copy to avoid corrupting original match object
                            match_copy.all_players = match.players.slice(0);
                            match_copy.players = [p];
                            //some data fields require computeMatchData in order to aggregate correctly
                            computeMatchData(match_copy);
                            //check for doc.players containing this match_id
                            //we could need to run aggregation if we are reinserting a match but this player used to be anonymous
                            var playerInMatch = doc && doc.players && doc.players.some(function(player) {
                                return player.account_id === p.account_id;
                            });
                            var reInsert = doc && options.type === "api" && playerInMatch;
                            //determine if we're reparsing this match
                            var reParse = doc && doc.parsed_data && options.type === "parsed";
                            if (!reInsert && !reParse && cache.aggData) {
                                //do aggregations on fields based on type
                                cache.aggData = aggregator([match_copy], options.type, cache.aggData);
                            }
                            //reduce match for display
                            //TODO if we want to cache full data, we don't want to get rid of player.parsedPlayer
                            reduceMatch(match_copy);
                            var orig = cache.data[match_copy.match_id];
                            if (!orig) {
                                cache.data[match_copy.match_id] = match_copy;
                            }
                            else {
                                //check if we can update old values
                                //use new parse status if 2
                                orig.parse_status = match_copy.parse_status === 2 ? match_copy.parse_status : orig.parse_status;
                                //use new players[] if parse_status===2
                                orig.players = match_copy.parse_status === 2 ? match_copy.players : orig.players;
                            }
                        }
                        else {
                            //update only, if type===skill
                            if (cache.data[match_copy.match_id]) {
                                cache.data[match_copy.match_id].skill = match_copy.skill;
                            }
                        }
                        redis.ttl("player:" + p.account_id, function(err, ttl) {
                            if (err) {
                                return cb(err);
                            }
                            redis.setex("player:" + p.account_id, Number(ttl), zlib.deflateSync(JSON.stringify(cache)).toString('base64'));
                        });
                    }
                    cb(err);
                    /*
                    //temporarily disable inserting new players into db
                    db.players.update({
                        account_id: p.account_id
                    }, {
                        $set: {
                            account_id: p.account_id,
                        }
                    }, {
                        upsert: true
                    }, function(err) {
                        if (err) {
                            return cb(err);
                        }
                        cb(err);
                    });
                    */
                });
            },
            //done with all 10 players
            function(err) {
                if (err) {
                    return cb(err);
                }
                //clear the match cache
                redis.del("match:" + match.match_id, function(err) {
                    return cb(err, doc);
                });
            });
    });
};<commit_msg>fail on redis cache retrieval error<commit_after>var utility = require('./utility');
var reduceMatch = utility.reduceMatch;
var aggregator = require('./aggregator');
var async = require('async');
var db = require('./db');
var r = require('./redis');
var redis = r.client;
var zlib = require('zlib');
var computeMatchData = require('./compute').computeMatchData;
module.exports = function updatePlayerCaches(match, options, cb) {
    //insert the match into db, then based on the existing document determine whether to do aggregations
    db.matches.findAndModify({
        match_id: match.match_id
    }, {
        $set: match
    }, {
        //don't upsert if inserting skill data
        upsert: options.type !== "skill",
        //explicitly declare we want the pre-modification document in the cb
        "new": false
    }, function(err, doc) {
        if (err) {
            return cb(err);
        }
        if (options.type === "skill" && !doc) {
            //we didn't add skill data, return immediately
            return cb(err);
        }
        async.each(match.players || options.players, function(p, cb) {
                redis.get("player:" + p.account_id, function(err, result) {
                    if (err) {
                        return cb(err);
                    }
                    //if player cache doesn't exist, skip
                    var cache = result ? JSON.parse(zlib.inflateSync(new Buffer(result, 'base64'))) : null;
                    if (cache) {
                        var match_copy = JSON.parse(JSON.stringify(match));
                        if (options.type !== "skill") {
                            //m.players[0] should be this player
                            //m.all_players should be all players
                            //duplicate this data into a copy to avoid corrupting original match object
                            match_copy.all_players = match.players.slice(0);
                            match_copy.players = [p];
                            //some data fields require computeMatchData in order to aggregate correctly
                            computeMatchData(match_copy);
                            //check for doc.players containing this match_id
                            //we could need to run aggregation if we are reinserting a match but this player used to be anonymous
                            var playerInMatch = doc && doc.players && doc.players.some(function(player) {
                                return player.account_id === p.account_id;
                            });
                            var reInsert = doc && options.type === "api" && playerInMatch;
                            //determine if we're reparsing this match
                            var reParse = doc && doc.parsed_data && options.type === "parsed";
                            if (!reInsert && !reParse && cache.aggData) {
                                //do aggregations on fields based on type
                                cache.aggData = aggregator([match_copy], options.type, cache.aggData);
                            }
                            //reduce match for display
                            //TODO if we want to cache full data, we don't want to get rid of player.parsedPlayer
                            reduceMatch(match_copy);
                            var orig = cache.data[match_copy.match_id];
                            if (!orig) {
                                cache.data[match_copy.match_id] = match_copy;
                            }
                            else {
                                //check if we can update old values
                                //use new parse status if 2
                                orig.parse_status = match_copy.parse_status === 2 ? match_copy.parse_status : orig.parse_status;
                                //use new players[] if parse_status===2
                                orig.players = match_copy.parse_status === 2 ? match_copy.players : orig.players;
                            }
                        }
                        else {
                            //update only, if type===skill
                            if (cache.data[match_copy.match_id]) {
                                cache.data[match_copy.match_id].skill = match_copy.skill;
                            }
                        }
                        redis.ttl("player:" + p.account_id, function(err, ttl) {
                            if (err) {
                                return cb(err);
                            }
                            redis.setex("player:" + p.account_id, Number(ttl), zlib.deflateSync(JSON.stringify(cache)).toString('base64'));
                        });
                    }
                    cb(err);
                    /*
                    //temporarily disable inserting new players into db on match insert
                    db.players.update({
                        account_id: p.account_id
                    }, {
                        $set: {
                            account_id: p.account_id,
                        }
                    }, {
                        upsert: true
                    }, function(err) {
                        if (err) {
                            return cb(err);
                        }
                        cb(err);
                    });
                    */
                });
            },
            //done with all 10 players
            function(err) {
                if (err) {
                    return cb(err);
                }
                //clear the match cache
                redis.del("match:" + match.match_id, function(err) {
                    return cb(err, doc);
                });
            });
    });
};<|endoftext|>