<commit_before>/* jslint node: true */
'use strict';

//  ENiGMA½
const Events                = require('./events.js');
const Config                = require('./config.js').get;
const {
    getConfigPath,
    getFullConfig,
}                           = require('./config_util.js');
const UserDb                = require('./database.js').dbs.user;
const {
    getISOTimestampString
}                           = require('./database.js');
const UserInterruptQueue    = require('./user_interrupt_queue.js');
const {
    getConnectionByUserId
}                           = require('./client_connections.js');
const UserProps             = require('./user_property.js');
const {
    Errors,
    ErrorReasons
}                           = require('./enig_error.js');
const { getThemeArt }       = require('./theme.js');
const { pipeToAnsi }        = require('./color_codes.js');
const stringFormat          = require('./string_format.js');
const StatLog               = require('./stat_log.js');
const Log                   = require('./logger.js').log;
const ConfigCache           = require('./config_cache.js');

//  deps
const _             = require('lodash');
const async         = require('async');
const moment        = require('moment');
const paths         = require('path');

class Achievement {
    constructor(data) {
        this.data = data;

        //  achievements are retroactive by default
        this.data.retroactive = _.get(this.data, 'retroactive', true);
    }

    static factory(data) {
        let achievement;
        switch(data.type) {
            case Achievement.Types.UserStatSet :
            case Achievement.Types.UserStatInc :
                achievement = new UserStatAchievement(data);
                break;

            default : return;
        }

        if(achievement.isValid()) {
            return achievement;
        }
    }

    static get Types() {
        return {
            UserStatSet : 'userStatSet',
            UserStatInc : 'userStatInc',
        };
    }

    isValid() {
        switch(this.data.type) {
            case Achievement.Types.UserStatSet :
            case Achievement.Types.UserStatInc :
                if(!_.isString(this.data.statName)) {
                    return false;
                }
                if(!_.isObject(this.data.match)) {
                    return false;
                }
                break;

            default : return false;
        }
        return true;
    }

    getMatchDetails(/*matchAgainst*/) {
    }

    isValidMatchDetails(details) {
        if(!details || !_.isString(details.title) || !_.isString(details.text) || !_.isNumber(details.points)) {
            return false;
        }
        return (_.isString(details.globalText) || !details.globalText);
    }
}

class UserStatAchievement extends Achievement {
    constructor(data) {
        super(data);

        //  sort match keys for quick match lookup
        this.matchKeys = Object.keys(this.data.match || {}).map(k => parseInt(k)).sort( (a, b) => b - a);
    }

    isValid() {
        if(!super.isValid()) {
            return false;
        }
        return !Object.keys(this.data.match).some(k => !parseInt(k));
    }

    getMatchDetails(matchValue) {
        let ret = [];
        let matchField = this.matchKeys.find(v => matchValue >= v);
        if(matchField) {
            const match = this.data.match[matchField];
            matchField = parseInt(matchField);
            if(this.isValidMatchDetails(match) && !isNaN(matchField)) {
                ret = [ match, matchField, matchValue ];
            }
        }
        return ret;
    }
}

class Achievements {
    constructor(events) {
        this.events = events;
    }

    init(cb) {
        let achievementConfigPath = _.get(Config(), 'general.achievementFile');
        if(!achievementConfigPath) {
            Log.info('Achievements are not configured');
            return cb(null);
        }
        achievementConfigPath = getConfigPath(achievementConfigPath);   //  qualify

        const configLoaded = (achievementConfig) => {
            if(true !== achievementConfig.enabled) {
                Log.info('Achievements are not enabled');
                this.stopMonitoringUserStatEvents();
                delete this.achievementConfig;
            } else {
                Log.info('Achievements are enabled');
                this.achievementConfig = achievementConfig;
                this.monitorUserStatEvents();
            }
        };

        const changed = ( { fileName, fileRoot } ) => {
            const reCachedPath = paths.join(fileRoot, fileName);
            if(reCachedPath === achievementConfigPath) {
                getFullConfig(achievementConfigPath, (err, achievementConfig) => {
                    if(err) {
                        return Log.error( { error : err.message }, 'Failed to reload achievement config from cache');
                    }
                    configLoaded(achievementConfig);
                });
            }
        };

        ConfigCache.getConfigWithOptions(
            {
                filePath        : achievementConfigPath,
                forceReCache    : true,
                callback        : changed,
            },
            (err, achievementConfig) => {
                if(err) {
                    return cb(err);
                }

                configLoaded(achievementConfig);
                return cb(null);
            }
        );
    }

    loadAchievementHitCount(user, achievementTag, field, cb) {
        UserDb.get(
            `SELECT COUNT() AS count
            FROM user_achievement
            WHERE user_id = ? AND achievement_tag = ? AND match = ?;`,
            [ user.userId, achievementTag, field],
            (err, row) => {
                return cb(err, row ? row.count : 0);
            }
        );
    }

    record(info, cb) {
        StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalCount, 1);
        StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalPoints, info.details.points);

        UserDb.run(
            `INSERT INTO user_achievement (user_id, achievement_tag, timestamp, match)
            VALUES (?, ?, ?, ?);`,
            [ info.client.user.userId, info.achievementTag, getISOTimestampString(info.timestamp), info.matchField ],
            err => {
                if(err) {
                    return cb(err);
                }

                this.events.emit(
                    Events.getSystemEvents().UserAchievementEarned,
                    {
                        user            : info.client.user,
                        achievementTag  : info.achievementTag,
                        points          : info.details.points,
                    }
                );

                return cb(null);
            }
        );
    }

    display(info, cb) {
        this.createAchievementInterruptItems(info, (err, interruptItems) => {
            if(err) {
                return cb(err);
            }

            if(interruptItems.local) {
                UserInterruptQueue.queue(interruptItems.local, { clients : info.client } );
            }

            if(interruptItems.global) {
                UserInterruptQueue.queue(interruptItems.global, { omit : info.client } );
            }

            return cb(null);
        });
    }

    recordAndDisplayAchievement(info, cb) {
        async.series(
            [
                (callback) => {
                    return this.record(info, callback);
                },
                (callback) => {
                    return this.display(info, callback);
                }
            ],
            err => {
                return cb(err);
            }
        );
    }

    monitorUserStatEvents() {
        if(this.userStatEventListeners) {
            return; //  already listening
        }

        const listenEvents = [
            Events.getSystemEvents().UserStatSet,
            Events.getSystemEvents().UserStatIncrement
        ];

        this.userStatEventListeners = this.events.addMultipleEventListener(listenEvents, userStatEvent => {
            if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
                return;
            }

            if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) {
                return;
            }

            //  :TODO: Make this code generic - find + return factory created object
            const achievementTag = _.findKey(
                _.get(this.achievementConfig, 'achievements', {}),
                achievement => {
                    if(false === achievement.enabled) {
                        return false;
                    }
                    return [ Achievement.Types.UserStatSet, Achievement.Types.UserStatInc ].includes(achievement.type) &&
                        achievement.statName === userStatEvent.statName;
                }
            );

            if(!achievementTag) {
                return;
            }

            const achievement = Achievement.factory(this.achievementConfig.achievements[achievementTag]);
            if(!achievement) {
                return;
            }

            const statValue = parseInt(Achievement.Types.UserStatSet === achievement.data.type ?
                userStatEvent.statValue :
                userStatEvent.statIncrementBy
            );
            if(isNaN(statValue)) {
                return;
            }

            const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue);
            if(!details) {
                return;
            }

            async.series(
                [
                    (callback) => {
                        this.loadAchievementHitCount(userStatEvent.user, achievementTag, matchField, (err, count) => {
                            if(err) {
                                return callback(err);
                            }
                            return callback(count > 0 ? Errors.General('Achievement already acquired', ErrorReasons.TooMany) : null);
                        });
                    },
                    (callback) => {
                        const client = getConnectionByUserId(userStatEvent.user.userId);
                        if(!client) {
                            return callback(Errors.UnexpectedState('Failed to get client for user ID'));
                        }

                        const info = {
                            achievementTag,
                            achievement,
                            details,
                            client,
                            matchField,                     //  match - may be in odd format
                            matchValue,                     //  actual value
                            achievedValue   : matchField,   //  achievement value met
                            user            : userStatEvent.user,
                            timestamp       : moment(),
                        };

                        const achievementsInfo = [ info ];
                        if(true === achievement.data.retroactive) {
                            //  For userStat, any lesser match keys(values) are also met. Example:
                            //  matchKeys: [ 500, 200, 100, 20, 10, 2 ]
                            //                    ^---- we met here
                            //                         ^------------^ retroactive range
                            //
                            const index = achievement.matchKeys.findIndex(v => v < matchField);
                            if(index > -1 && Array.isArray(achievement.matchKeys)) {
                                achievement.matchKeys.slice(index).forEach(k => {
                                    const [ det, fld, val ] = achievement.getMatchDetails(k);
                                    if(det) {
                                        achievementsInfo.push(Object.assign(
                                            {},
                                            info,
                                            {
                                                details         : det,
                                                matchField      : fld,
                                                achievedValue   : fld,
                                                matchValue      : val,
                                            }
                                        ));
                                    }
                                });
                            }
                        }

                        //  reverse achievementsInfo so we display smallest > largest
                        achievementsInfo.reverse();

                        async.eachSeries(achievementsInfo, (achInfo, nextAchInfo) => {
                            return this.recordAndDisplayAchievement(achInfo, err => {
                                return nextAchInfo(err);
                            });
                        },
                        err => {
                            return callback(err);
                        });
                    }
                ],
                err => {
                    if(err && ErrorReasons.TooMany !== err.reasonCode) {
                        Log.warn( { error : err.message, userStatEvent }, 'Error handling achievement for user stat event');
                    }
                }
            );
        });
    }

    stopMonitoringUserStatEvents() {
        if(this.userStatEventListeners) {
            this.events.removeMultipleEventListener(this.userStatEventListeners);
            delete this.userStatEventListeners;
        }
    }

    getFormatObject(info) {
        return {
            userName        : info.user.username,
            userRealName    : info.user.properties[UserProps.RealName],
            userLocation    : info.user.properties[UserProps.Location],
            userAffils      : info.user.properties[UserProps.Affiliations],
            nodeId          : info.client.node,
            title           : info.details.title,
            text            : info.global ? info.details.globalText : info.details.text,
            points          : info.details.points,
            achievedValue   : info.achievedValue,
            matchField      : info.matchField,
            matchValue      : info.matchValue,
            timestamp       : moment(info.timestamp).format(info.dateTimeFormat),
            boardName       : Config().general.boardName,
        };
    }

    getFormattedTextFor(info, textType, defaultSgr = '|07') {
        const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
        const textTypeSgr   = themeDefaults[`${textType}SGR`] || defaultSgr;

        const formatObj = this.getFormatObject(info);

        const wrap = (input) => {
            const re = new RegExp(`{(${Object.keys(formatObj).join('|')})([^}]*)}`, 'g');
            return input.replace(re, (m, formatVar, formatOpts) => {
                const varSgr = themeDefaults[`${formatVar}SGR`] || textTypeSgr;
                let r = `${varSgr}{${formatVar}`;
                if(formatOpts) {
                    r += formatOpts;
                }
                return `${r}}${textTypeSgr}`;
            });
        };

        return stringFormat(`${textTypeSgr}${wrap(info.details[textType])}`, formatObj);
    }

    createAchievementInterruptItems(info, cb) {
        info.dateTimeFormat =
            info.details.dateTimeFormat ||
            info.achievement.dateTimeFormat ||
            info.client.currentTheme.helpers.getDateTimeFormat();

        const title = this.getFormattedTextFor(info, 'title');
        const text  = this.getFormattedTextFor(info, 'text');

        let globalText;
        if(info.details.globalText) {
            globalText = this.getFormattedTextFor(info, 'globalText');
        }

        const getArt = (name, callback) => {
            const spec =
                _.get(info.details, `art.${name}`) ||
                _.get(info.achievement, `art.${name}`) ||
                _.get(this.achievementConfig, `art.${name}`);
            if(!spec) {
                return callback(null);
            }
            const getArtOpts = {
                name    : spec,
                client  : this.client,
                random  : false,
            };
            getThemeArt(getArtOpts, (err, artInfo) => {
                //  ignore errors
                return callback(artInfo ? artInfo.data : null);
            });
        };

        const interruptItems = {};
        let itemTypes = [ 'local' ];
        if(globalText) {
            itemTypes.push('global');
        }

        async.each(itemTypes, (itemType, nextItemType) => {
            async.waterfall(
                [
                    (callback) => {
                        getArt(`${itemType}Header`, headerArt => {
                            return callback(null, headerArt);
                        });
                    },
                    (headerArt, callback) => {
                        getArt(`${itemType}Footer`, footerArt => {
                            return callback(null, headerArt, footerArt);
                        });
                    },
                    (headerArt, footerArt, callback) => {
                        const itemText = 'global' === itemType ? globalText : text;
                        interruptItems[itemType] = {
                            text    : `${title}\r\n${itemText}`,
                            pause   : true,
                        };
                        if(headerArt || footerArt) {
                            const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
                            const defaultContentsFormat = '{title}\r\n{message}';
                            const contentsFormat = 'global' === itemType ?
                                themeDefaults.globalFormat || defaultContentsFormat :
                                themeDefaults.format || defaultContentsFormat;

                            const formatObj = Object.assign(this.getFormatObject(info), {
                                title   : this.getFormattedTextFor(info, 'title', ''),  //  ''=defaultSgr
                                message : itemText,
                            });

                            const contents = pipeToAnsi(stringFormat(contentsFormat, formatObj));

                            interruptItems[itemType].contents =
                                `${headerArt || ''}\r\n${contents}\r\n${footerArt || ''}`;
                        }
                        return callback(null);
                    }
                ],
                err => {
                    return nextItemType(err);
                }
            );
        },
        err => {
            return cb(err, interruptItems);
        });
    }
}

let achievements;

exports.moduleInitialize = (initInfo, cb) => {
    achievements = new Achievements(initInfo.events);
    return achievements.init(cb);
};
<commit_msg>Fix INSERT clause for cases of overlap<commit_after>/* jslint node: true */
'use strict';

//  ENiGMA½
const Events                = require('./events.js');
const Config                = require('./config.js').get;
const {
    getConfigPath,
    getFullConfig,
}                           = require('./config_util.js');
const UserDb                = require('./database.js').dbs.user;
const {
    getISOTimestampString
}                           = require('./database.js');
const UserInterruptQueue    = require('./user_interrupt_queue.js');
const {
    getConnectionByUserId
}                           = require('./client_connections.js');
const UserProps             = require('./user_property.js');
const {
    Errors,
    ErrorReasons
}                           = require('./enig_error.js');
const { getThemeArt }       = require('./theme.js');
const { pipeToAnsi }        = require('./color_codes.js');
const stringFormat          = require('./string_format.js');
const StatLog               = require('./stat_log.js');
const Log                   = require('./logger.js').log;
const ConfigCache           = require('./config_cache.js');

//  deps
const _             = require('lodash');
const async         = require('async');
const moment        = require('moment');
const paths         = require('path');

class Achievement {
    constructor(data) {
        this.data = data;

        //  achievements are retroactive by default
        this.data.retroactive = _.get(this.data, 'retroactive', true);
    }

    static factory(data) {
        let achievement;
        switch(data.type) {
            case Achievement.Types.UserStatSet :
            case Achievement.Types.UserStatInc :
                achievement = new UserStatAchievement(data);
                break;

            default : return;
        }

        if(achievement.isValid()) {
            return achievement;
        }
    }

    static get Types() {
        return {
            UserStatSet : 'userStatSet',
            UserStatInc : 'userStatInc',
        };
    }

    isValid() {
        switch(this.data.type) {
            case Achievement.Types.UserStatSet :
            case Achievement.Types.UserStatInc :
                if(!_.isString(this.data.statName)) {
                    return false;
                }
                if(!_.isObject(this.data.match)) {
                    return false;
                }
                break;

            default : return false;
        }
        return true;
    }

    getMatchDetails(/*matchAgainst*/) {
    }

    isValidMatchDetails(details) {
        if(!details || !_.isString(details.title) || !_.isString(details.text) || !_.isNumber(details.points)) {
            return false;
        }
        return (_.isString(details.globalText) || !details.globalText);
    }
}

class UserStatAchievement extends Achievement {
    constructor(data) {
        super(data);

        //  sort match keys for quick match lookup
        this.matchKeys = Object.keys(this.data.match || {}).map(k => parseInt(k)).sort( (a, b) => b - a);
    }

    isValid() {
        if(!super.isValid()) {
            return false;
        }
        return !Object.keys(this.data.match).some(k => !parseInt(k));
    }

    getMatchDetails(matchValue) {
        let ret = [];
        let matchField = this.matchKeys.find(v => matchValue >= v);
        if(matchField) {
            const match = this.data.match[matchField];
            matchField = parseInt(matchField);
            if(this.isValidMatchDetails(match) && !isNaN(matchField)) {
                ret = [ match, matchField, matchValue ];
            }
        }
        return ret;
    }
}

class Achievements {
    constructor(events) {
        this.events = events;
    }

    init(cb) {
        let achievementConfigPath = _.get(Config(), 'general.achievementFile');
        if(!achievementConfigPath) {
            Log.info('Achievements are not configured');
            return cb(null);
        }
        achievementConfigPath = getConfigPath(achievementConfigPath);   //  qualify

        const configLoaded = (achievementConfig) => {
            if(true !== achievementConfig.enabled) {
                Log.info('Achievements are not enabled');
                this.stopMonitoringUserStatEvents();
                delete this.achievementConfig;
            } else {
                Log.info('Achievements are enabled');
                this.achievementConfig = achievementConfig;
                this.monitorUserStatEvents();
            }
        };

        const changed = ( { fileName, fileRoot } ) => {
            const reCachedPath = paths.join(fileRoot, fileName);
            if(reCachedPath === achievementConfigPath) {
                getFullConfig(achievementConfigPath, (err, achievementConfig) => {
                    if(err) {
                        return Log.error( { error : err.message }, 'Failed to reload achievement config from cache');
                    }
                    configLoaded(achievementConfig);
                });
            }
        };

        ConfigCache.getConfigWithOptions(
            {
                filePath        : achievementConfigPath,
                forceReCache    : true,
                callback        : changed,
            },
            (err, achievementConfig) => {
                if(err) {
                    return cb(err);
                }

                configLoaded(achievementConfig);
                return cb(null);
            }
        );
    }

    loadAchievementHitCount(user, achievementTag, field, cb) {
        UserDb.get(
            `SELECT COUNT() AS count
            FROM user_achievement
            WHERE user_id = ? AND achievement_tag = ? AND match = ?;`,
            [ user.userId, achievementTag, field],
            (err, row) => {
                return cb(err, row ? row.count : 0);
            }
        );
    }

    record(info, cb) {
        StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalCount, 1);
        StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalPoints, info.details.points);

        UserDb.run(
            `INSERT OR IGNORE INTO user_achievement (user_id, achievement_tag, timestamp, match)
            VALUES (?, ?, ?, ?);`,
            [ info.client.user.userId, info.achievementTag, getISOTimestampString(info.timestamp), info.matchField ],
            err => {
                if(err) {
                    return cb(err);
                }

                this.events.emit(
                    Events.getSystemEvents().UserAchievementEarned,
                    {
                        user            : info.client.user,
                        achievementTag  : info.achievementTag,
                        points          : info.details.points,
                    }
                );

                return cb(null);
            }
        );
    }

    display(info, cb) {
        this.createAchievementInterruptItems(info, (err, interruptItems) => {
            if(err) {
                return cb(err);
            }

            if(interruptItems.local) {
                UserInterruptQueue.queue(interruptItems.local, { clients : info.client } );
            }

            if(interruptItems.global) {
                UserInterruptQueue.queue(interruptItems.global, { omit : info.client } );
            }

            return cb(null);
        });
    }

    recordAndDisplayAchievement(info, cb) {
        async.series(
            [
                (callback) => {
                    return this.record(info, callback);
                },
                (callback) => {
                    return this.display(info, callback);
                }
            ],
            err => {
                return cb(err);
            }
        );
    }

    monitorUserStatEvents() {
        if(this.userStatEventListeners) {
            return; //  already listening
        }

        const listenEvents = [
            Events.getSystemEvents().UserStatSet,
            Events.getSystemEvents().UserStatIncrement
        ];

        this.userStatEventListeners = this.events.addMultipleEventListener(listenEvents, userStatEvent => {
            if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
                return;
            }

            if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) {
                return;
            }

            //  :TODO: Make this code generic - find + return factory created object
            const achievementTag = _.findKey(
                _.get(this.achievementConfig, 'achievements', {}),
                achievement => {
                    if(false === achievement.enabled) {
                        return false;
                    }
                    return [ Achievement.Types.UserStatSet, Achievement.Types.UserStatInc ].includes(achievement.type) &&
                        achievement.statName === userStatEvent.statName;
                }
            );

            if(!achievementTag) {
                return;
            }

            const achievement = Achievement.factory(this.achievementConfig.achievements[achievementTag]);
            if(!achievement) {
                return;
            }

            const statValue = parseInt(Achievement.Types.UserStatSet === achievement.data.type ?
                userStatEvent.statValue :
                userStatEvent.statIncrementBy
            );
            if(isNaN(statValue)) {
                return;
            }

            const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue);
            if(!details) {
                return;
            }

            async.series(
                [
                    (callback) => {
                        this.loadAchievementHitCount(userStatEvent.user, achievementTag, matchField, (err, count) => {
                            if(err) {
                                return callback(err);
                            }
                            return callback(count > 0 ? Errors.General('Achievement already acquired', ErrorReasons.TooMany) : null);
                        });
                    },
                    (callback) => {
                        const client = getConnectionByUserId(userStatEvent.user.userId);
                        if(!client) {
                            return callback(Errors.UnexpectedState('Failed to get client for user ID'));
                        }

                        const info = {
                            achievementTag,
                            achievement,
                            details,
                            client,
                            matchField,                     //  match - may be in odd format
                            matchValue,                     //  actual value
                            achievedValue   : matchField,   //  achievement value met
                            user            : userStatEvent.user,
                            timestamp       : moment(),
                        };

                        const achievementsInfo = [ info ];
                        if(true === achievement.data.retroactive) {
                            //  For userStat, any lesser match keys(values) are also met. Example:
                            //  matchKeys: [ 500, 200, 100, 20, 10, 2 ]
                            //                    ^---- we met here
                            //                         ^------------^ retroactive range
                            //
                            const index = achievement.matchKeys.findIndex(v => v < matchField);
                            if(index > -1 && Array.isArray(achievement.matchKeys)) {
                                achievement.matchKeys.slice(index).forEach(k => {
                                    const [ det, fld, val ] = achievement.getMatchDetails(k);
                                    if(det) {
                                        achievementsInfo.push(Object.assign(
                                            {},
                                            info,
                                            {
                                                details         : det,
                                                matchField      : fld,
                                                achievedValue   : fld,
                                                matchValue      : val,
                                            }
                                        ));
                                    }
                                });
                            }
                        }

                        //  reverse achievementsInfo so we display smallest > largest
                        achievementsInfo.reverse();

                        async.eachSeries(achievementsInfo, (achInfo, nextAchInfo) => {
                            return this.recordAndDisplayAchievement(achInfo, err => {
                                return nextAchInfo(err);
                            });
                        },
                        err => {
                            return callback(err);
                        });
                    }
                ],
                err => {
                    if(err && ErrorReasons.TooMany !== err.reasonCode) {
                        Log.warn( { error : err.message, userStatEvent }, 'Error handling achievement for user stat event');
                    }
                }
            );
        });
    }

    stopMonitoringUserStatEvents() {
        if(this.userStatEventListeners) {
            this.events.removeMultipleEventListener(this.userStatEventListeners);
            delete this.userStatEventListeners;
        }
    }

    getFormatObject(info) {
        return {
            userName        : info.user.username,
            userRealName    : info.user.properties[UserProps.RealName],
            userLocation    : info.user.properties[UserProps.Location],
            userAffils      : info.user.properties[UserProps.Affiliations],
            nodeId          : info.client.node,
            title           : info.details.title,
            text            : info.global ? info.details.globalText : info.details.text,
            points          : info.details.points,
            achievedValue   : info.achievedValue,
            matchField      : info.matchField,
            matchValue      : info.matchValue,
            timestamp       : moment(info.timestamp).format(info.dateTimeFormat),
            boardName       : Config().general.boardName,
        };
    }

    getFormattedTextFor(info, textType, defaultSgr = '|07') {
        const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
        const textTypeSgr   = themeDefaults[`${textType}SGR`] || defaultSgr;

        const formatObj = this.getFormatObject(info);

        const wrap = (input) => {
            const re = new RegExp(`{(${Object.keys(formatObj).join('|')})([^}]*)}`, 'g');
            return input.replace(re, (m, formatVar, formatOpts) => {
                const varSgr = themeDefaults[`${formatVar}SGR`] || textTypeSgr;
                let r = `${varSgr}{${formatVar}`;
                if(formatOpts) {
                    r += formatOpts;
                }
                return `${r}}${textTypeSgr}`;
            });
        };

        return stringFormat(`${textTypeSgr}${wrap(info.details[textType])}`, formatObj);
    }

    createAchievementInterruptItems(info, cb) {
        info.dateTimeFormat =
            info.details.dateTimeFormat ||
            info.achievement.dateTimeFormat ||
            info.client.currentTheme.helpers.getDateTimeFormat();

        const title = this.getFormattedTextFor(info, 'title');
        const text  = this.getFormattedTextFor(info, 'text');

        let globalText;
        if(info.details.globalText) {
            globalText = this.getFormattedTextFor(info, 'globalText');
        }

        const getArt = (name, callback) => {
            const spec =
                _.get(info.details, `art.${name}`) ||
                _.get(info.achievement, `art.${name}`) ||
                _.get(this.achievementConfig, `art.${name}`);
            if(!spec) {
                return callback(null);
            }
            const getArtOpts = {
                name    : spec,
                client  : this.client,
                random  : false,
            };
            getThemeArt(getArtOpts, (err, artInfo) => {
                //  ignore errors
                return callback(artInfo ? artInfo.data : null);
            });
        };

        const interruptItems = {};
        let itemTypes = [ 'local' ];
        if(globalText) {
            itemTypes.push('global');
        }

        async.each(itemTypes, (itemType, nextItemType) => {
            async.waterfall(
                [
                    (callback) => {
                        getArt(`${itemType}Header`, headerArt => {
                            return callback(null, headerArt);
                        });
                    },
                    (headerArt, callback) => {
                        getArt(`${itemType}Footer`, footerArt => {
                            return callback(null, headerArt, footerArt);
                        });
                    },
                    (headerArt, footerArt, callback) => {
                        const itemText = 'global' === itemType ? globalText : text;
                        interruptItems[itemType] = {
                            text    : `${title}\r\n${itemText}`,
                            pause   : true,
                        };
                        if(headerArt || footerArt) {
                            const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
                            const defaultContentsFormat = '{title}\r\n{message}';
                            const contentsFormat = 'global' === itemType ?
                                themeDefaults.globalFormat || defaultContentsFormat :
                                themeDefaults.format || defaultContentsFormat;

                            const formatObj = Object.assign(this.getFormatObject(info), {
                                title   : this.getFormattedTextFor(info, 'title', ''),  //  ''=defaultSgr
                                message : itemText,
                            });

                            const contents = pipeToAnsi(stringFormat(contentsFormat, formatObj));

                            interruptItems[itemType].contents =
                                `${headerArt || ''}\r\n${contents}\r\n${footerArt || ''}`;
                        }
                        return callback(null);
                    }
                ],
                err => {
                    return nextItemType(err);
                }
            );
        },
        err => {
            return cb(err, interruptItems);
        });
    }
}

let achievements;

exports.moduleInitialize = (initInfo, cb) => {
    achievements = new Achievements(initInfo.events);
    return achievements.init(cb);
};
<|endoftext|>


<commit_before>///import core
/**
 * @description 插入内容
 * @name baidu.editor.execCommand
 * @param   {String}   cmdName     inserthtml插入内容的命令
 * @param   {String}   html                要插入的内容
 * @author zhanyi
 */
UE.commands['inserthtml'] = {
    execCommand: function (command,html,notSerialize){
        var me = this,
            range,
            div;
        if(!html){
            return;
        }
        range = me.selection.getRange();
        div = range.document.createElement( 'div' );
        div.style.display = 'inline';

        if (!notSerialize) {
            var root = UE.htmlparser(html);
            //如果给了过滤规则就先进行过滤
            if(me.options.filterRules){
                UE.filterNode(root,me.options.filterRules);
            }
            //执行默认的处理
            me.filterInputRule(root);
            html = root.toHtml()
        }
        div.innerHTML = utils.trim( html );

        if ( !range.collapsed ) {
            var tmpNode = range.startContainer;
            if(domUtils.isFillChar(tmpNode)){
                range.setStartBefore(tmpNode)
            }
            tmpNode = range.endContainer;
            if(domUtils.isFillChar(tmpNode)){
                range.setEndAfter(tmpNode)
            }
            range.txtToElmBoundary();
            //结束边界可能放到了br的前边，要把br包含进来
            // x[xxx]

            if(range.endContainer && range.endContainer.nodeType == 1){
                tmpNode = range.endContainer.childNodes[range.endOffset];
                if(tmpNode && domUtils.isBr(tmpNode)){
                    range.setEndAfter(tmpNode);
                }
            }
            if(range.startOffset == 0){
                tmpNode = range.startContainer;
                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
                    tmpNode = range.endContainer;
                    if(range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
                        me.body.innerHTML = '
'+(browser.ie ? '' : '
')+'
';
                        range.setStart(me.body.firstChild,0).collapse(true)

                    }
                }
            }
            !range.collapsed && range.deleteContents();
            if(range.startContainer.nodeType == 1){
                var child = range.startContainer.childNodes[range.startOffset],pre;
                if(child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)){
                    range.setEnd(pre,pre.childNodes.length).collapse();
                    while(child.firstChild){
                        pre.appendChild(child.firstChild);
                    }
                    domUtils.remove(child);
                }
            }

        }

        var child,parent,pre,tmp,hadBreak = 0, nextNode;
        //如果当前位置选中了fillchar要干掉，要不会产生空行
        if(range.inFillChar()){
            child = range.startContainer;
            range.setStartBefore(child).collapse(true);
            domUtils.remove(child);
        }
        while ( child = div.firstChild ) {
            range.insertNode( child );
            nextNode = child.nextSibling;
            if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm( child ) ){

                parent = domUtils.findParent( child,function ( node ){ return domUtils.isBlockElm( node ); } );
                if ( parent && parent.tagName.toLowerCase() != 'body' && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)){
                    if(!dtd[parent.tagName][child.nodeName]){
                        pre = parent;
                    }else{
                        tmp = child.parentNode;
                        while (tmp !== parent){
                            pre = tmp;
                            tmp = tmp.parentNode;

                        }
                    }

                    domUtils.breakParent( child, pre || tmp );
                    //去掉break后前一个多余的节点
|<[p> ==>

|

                    var pre = child.previousSibling;
                    domUtils.trimWhiteTextNode(pre);
                    if(!pre.childNodes.length){
                        domUtils.remove(pre);
                    }
                    //trace:2012,在非ie的情况，切开后剩下的节点有可能不能点入光标添加br占位

                    if(!browser.ie &&
                        (next = child.nextSibling) &&
                        domUtils.isBlockElm(next) &&
                        next.lastChild &&
                        !domUtils.isBr(next.lastChild)){
                        next.appendChild(me.document.createElement('br'));
                    }
                    hadBreak = 1;
                }
            }
            var next = child.nextSibling;
            if(!div.firstChild && next && domUtils.isBlockElm(next)){

                range.setStart(next,0).collapse(true);
                break;
            }
            range.setEndAfter( child ).collapse();

        }

        child = range.startContainer;

        if(nextNode && domUtils.isBr(nextNode)){
            domUtils.remove(nextNode)
        }
        //用chrome可能有空白展位符
        if(domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)){
            if(nextNode = child.nextSibling){
                domUtils.remove(child);
                if(nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]){

                    range.setStart(nextNode,0).collapse(true).shrinkBoundary()
                }
            }else{
                child.innerHTML = browser.ie ? domUtils.fillChar : '
';
            }

        }
        //加上true因为在删除表情等时会删两次，第一次是删的fillData
        range.select(true);

        setTimeout(function(){
            range = me.selection.getRange();
            range.scrollToView(me.autoHeightEnabled,me.autoHeightEnabled ? domUtils.getXY(me.iframe).y:0);
            me.fireEvent('afterinserthtml');
        },200);
    }
};
<commit_msg>by zhanyi<commit_after>///import core
/**
 * @description 插入内容
 * @name baidu.editor.execCommand
 * @param   {String}   cmdName     inserthtml插入内容的命令
 * @param   {String}   html                要插入的内容
 * @author zhanyi
 */
UE.commands['inserthtml'] = {
    execCommand: function (command,html,notSerialize){
        var me = this,
            range,
            div;
        if(!html){
            return;
        }
        range = me.selection.getRange();
        div = range.document.createElement( 'div' );
        div.style.display = 'inline';

        if (!notSerialize) {
            var root = UE.htmlparser(html);
            //如果给了过滤规则就先进行过滤
            if(me.options.filterRules){
                UE.filterNode(root,me.options.filterRules);
            }
            //执行默认的处理
            me.filterInputRule(root);
            html = root.toHtml()
        }
        div.innerHTML = utils.trim( html );

        if ( !range.collapsed ) {
            var tmpNode = range.startContainer;
            if(domUtils.isFillChar(tmpNode)){
                range.setStartBefore(tmpNode)
            }
            tmpNode = range.endContainer;
            if(domUtils.isFillChar(tmpNode)){
                range.setEndAfter(tmpNode)
            }
            range.txtToElmBoundary();
            //结束边界可能放到了br的前边，要把br包含进来
            // x[xxx]

            if(range.endContainer && range.endContainer.nodeType == 1){
                tmpNode = range.endContainer.childNodes[range.endOffset];
                if(tmpNode && domUtils.isBr(tmpNode)){
                    range.setEndAfter(tmpNode);
                }
            }
            if(range.startOffset == 0){
                tmpNode = range.startContainer;
                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
                    tmpNode = range.endContainer;
                    if(range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
                        me.body.innerHTML = '
'+(browser.ie ? '' : '
')+'
';
                        range.setStart(me.body.firstChild,0).collapse(true)

                    }
                }
            }
            !range.collapsed && range.deleteContents();
            if(range.startContainer.nodeType == 1){
                var child = range.startContainer.childNodes[range.startOffset],pre;
                if(child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)){
                    range.setEnd(pre,pre.childNodes.length).collapse();
                    while(child.firstChild){
                        pre.appendChild(child.firstChild);
                    }
                    domUtils.remove(child);
                }
            }

        }

        var child,parent,pre,tmp,hadBreak = 0, nextNode;
        //如果当前位置选中了fillchar要干掉，要不会产生空行
        if(range.inFillChar()){
            child = range.startContainer;
            if(domUtils.isFillChar(child)){
                range.setStartBefore(child).collapse(true);
                domUtils.remove(child);
            }else if(domUtils.isFillChar(child,true)){
                child.nodeValue = child.nodeValue.replace(fillCharReg,'');
                range.startOffset--;
                range.collapsed && range.collapse(true)
            }
        }
        while ( child = div.firstChild ) {
            range.insertNode( child );
            nextNode = child.nextSibling;
            if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm( child ) ){

                parent = domUtils.findParent( child,function ( node ){ return domUtils.isBlockElm( node ); } );
                if ( parent && parent.tagName.toLowerCase() != 'body' && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)){
                    if(!dtd[parent.tagName][child.nodeName]){
                        pre = parent;
                    }else{
                        tmp = child.parentNode;
                        while (tmp !== parent){
                            pre = tmp;
                            tmp = tmp.parentNode;

                        }
                    }

                    domUtils.breakParent( child, pre || tmp );
                    //去掉break后前一个多余的节点
|<[p> ==>

|

                    var pre = child.previousSibling;
                    domUtils.trimWhiteTextNode(pre);
                    if(!pre.childNodes.length){
                        domUtils.remove(pre);
                    }
                    //trace:2012,在非ie的情况，切开后剩下的节点有可能不能点入光标添加br占位

                    if(!browser.ie &&
                        (next = child.nextSibling) &&
                        domUtils.isBlockElm(next) &&
                        next.lastChild &&
                        !domUtils.isBr(next.lastChild)){
                        next.appendChild(me.document.createElement('br'));
                    }
                    hadBreak = 1;
                }
            }
            var next = child.nextSibling;
            if(!div.firstChild && next && domUtils.isBlockElm(next)){

                range.setStart(next,0).collapse(true);
                break;
            }
            range.setEndAfter( child ).collapse();

        }

        child = range.startContainer;

        if(nextNode && domUtils.isBr(nextNode)){
            domUtils.remove(nextNode)
        }
        //用chrome可能有空白展位符
        if(domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)){
            if(nextNode = child.nextSibling){
                domUtils.remove(child);
                if(nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]){

                    range.setStart(nextNode,0).collapse(true).shrinkBoundary()
                }
            }else{
                child.innerHTML = browser.ie ? domUtils.fillChar : '
';
            }

        }
        //加上true因为在删除表情等时会删两次，第一次是删的fillData
        range.select(true);

        setTimeout(function(){
            range = me.selection.getRange();
            range.scrollToView(me.autoHeightEnabled,me.autoHeightEnabled ? domUtils.getXY(me.iframe).y:0);
            me.fireEvent('afterinserthtml');
        },200);
    }
};
<|endoftext|>


<commit_before>const qs = require('querystring')
const _ = require('lodash')
const crypto = require('crypto')
const path = require('path')

const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
// eslint-disable-next-line no-control-regex
const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/

const contentToExt = {
  markdown: 'md',
  html: 'html'
}
const extToContent = _.invert(contentToExt)

/* global WIKI */

module.exports = {
  /**
   * Parse raw url path and make it safe
   */
  parsePath (rawPath, opts = {}) {
    let pathObj = {
      locale: WIKI.config.lang.code,
      path: 'home',
      private: false,
      privateNS: '',
      explicitLocale: false
    }

    // Clean Path
    rawPath = _.trim(qs.unescape(rawPath))
    if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }
    rawPath = rawPath.replace(unsafeCharsRegex, '')
    if (rawPath === '') { rawPath = 'home' }

    rawPath = rawPath.replaceAll('\\', '').replaceAll('//', '').replaceAll(/\.\.+/ig, '')

    // Extract Info
    let pathParts = _.filter(_.split(rawPath, '/'), p => {
      p = _.trim(p)
      return !_.isEmpty(p) && p !== '..' && p !== '.'
    })
    if (pathParts[0].length === 1) {
      pathParts.shift()
    }
    if (localeSegmentRegex.test(pathParts[0])) {
      pathObj.locale = pathParts[0]
      pathObj.explicitLocale = true
      pathParts.shift()
    }

    // Strip extension
    if (opts.stripExt && pathParts.length > 0) {
      const lastPart = _.last(pathParts)
      if (lastPart.indexOf('.') > 0) {
        pathParts.pop()
        const lastPartMeta = path.parse(lastPart)
        pathParts.push(lastPartMeta.name)
      }
    }

    pathObj.path = _.join(pathParts, '/')
    return pathObj
  },
  /**
   * Generate unique hash from page
   */
  generateHash(opts) {
    return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
  },
  /**
   * Inject Page Metadata
   */
  injectPageMetadata(page) {
    let meta = [
      ['title', page.title],
      ['description', page.description],
      ['published', page.isPublished.toString()],
      ['date', page.updatedAt],
      ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
      ['editor', page.editorKey],
      ['dateCreated', page.createdAt]
    ]
    switch (page.contentType) {
      case 'markdown':
        return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
      case 'html':
        return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
      case 'json':
        return {
          ...page.content,
          _meta: _.fromPairs(meta)
        }
      default:
        return page.content
    }
  },
  /**
   * Check if path is a reserved path
   */
  isReservedPath(rawPath) {
    const firstSection = _.head(rawPath.split('/'))
    if (firstSection.length <= 1) {
      return true
    } else if (localeSegmentRegex.test(firstSection)) {
      return true
    } else if (
      _.some(WIKI.data.reservedPaths, p => {
        return p === firstSection
      })) {
      return true
    } else {
      return false
    }
  },
  /**
   * Get file extension from content type
   */
  getFileExtension(contentType) {
    return _.get(contentToExt, contentType, 'txt')
  },
  /**
   * Get content type from file extension
   */
  getContentType (filePath) {
    const ext = _.last(filePath.split('.'))
    return _.get(extToContent, ext, false)
  },
  /**
   * Get Page Meta object from disk path
   */
  getPagePath (filePath) {
    let fpath = filePath
    if (process.platform === 'win32') {
      fpath = filePath.replace(/\\/g, '/')
    }
    let meta = {
      locale: WIKI.config.lang.code,
      path: _.initial(fpath.split('.')).join('')
    }
    const result = localeFolderRegex.exec(meta.path)
    if (result[1]) {
      meta = {
        locale: result[1].replace('/', ''),
        path: result[2]
      }
    }
    return meta
  }
}
<commit_msg>fix: use replace instead of replaceAll to support pre-Node16<commit_after>const qs = require('querystring')
const _ = require('lodash')
const crypto = require('crypto')
const path = require('path')

const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
// eslint-disable-next-line no-control-regex
const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/

const contentToExt = {
  markdown: 'md',
  html: 'html'
}
const extToContent = _.invert(contentToExt)

/* global WIKI */

module.exports = {
  /**
   * Parse raw url path and make it safe
   */
  parsePath (rawPath, opts = {}) {
    let pathObj = {
      locale: WIKI.config.lang.code,
      path: 'home',
      private: false,
      privateNS: '',
      explicitLocale: false
    }

    // Clean Path
    rawPath = _.trim(qs.unescape(rawPath))
    if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }
    rawPath = rawPath.replace(unsafeCharsRegex, '')
    if (rawPath === '') { rawPath = 'home' }

    rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')

    // Extract Info
    let pathParts = _.filter(_.split(rawPath, '/'), p => {
      p = _.trim(p)
      return !_.isEmpty(p) && p !== '..' && p !== '.'
    })
    if (pathParts[0].length === 1) {
      pathParts.shift()
    }
    if (localeSegmentRegex.test(pathParts[0])) {
      pathObj.locale = pathParts[0]
      pathObj.explicitLocale = true
      pathParts.shift()
    }

    // Strip extension
    if (opts.stripExt && pathParts.length > 0) {
      const lastPart = _.last(pathParts)
      if (lastPart.indexOf('.') > 0) {
        pathParts.pop()
        const lastPartMeta = path.parse(lastPart)
        pathParts.push(lastPartMeta.name)
      }
    }

    pathObj.path = _.join(pathParts, '/')
    return pathObj
  },
  /**
   * Generate unique hash from page
   */
  generateHash(opts) {
    return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
  },
  /**
   * Inject Page Metadata
   */
  injectPageMetadata(page) {
    let meta = [
      ['title', page.title],
      ['description', page.description],
      ['published', page.isPublished.toString()],
      ['date', page.updatedAt],
      ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
      ['editor', page.editorKey],
      ['dateCreated', page.createdAt]
    ]
    switch (page.contentType) {
      case 'markdown':
        return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
      case 'html':
        return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
      case 'json':
        return {
          ...page.content,
          _meta: _.fromPairs(meta)
        }
      default:
        return page.content
    }
  },
  /**
   * Check if path is a reserved path
   */
  isReservedPath(rawPath) {
    const firstSection = _.head(rawPath.split('/'))
    if (firstSection.length <= 1) {
      return true
    } else if (localeSegmentRegex.test(firstSection)) {
      return true
    } else if (
      _.some(WIKI.data.reservedPaths, p => {
        return p === firstSection
      })) {
      return true
    } else {
      return false
    }
  },
  /**
   * Get file extension from content type
   */
  getFileExtension(contentType) {
    return _.get(contentToExt, contentType, 'txt')
  },
  /**
   * Get content type from file extension
   */
  getContentType (filePath) {
    const ext = _.last(filePath.split('.'))
    return _.get(extToContent, ext, false)
  },
  /**
   * Get Page Meta object from disk path
   */
  getPagePath (filePath) {
    let fpath = filePath
    if (process.platform === 'win32') {
      fpath = filePath.replace(/\\/g, '/')
    }
    let meta = {
      locale: WIKI.config.lang.code,
      path: _.initial(fpath.split('.')).join('')
    }
    const result = localeFolderRegex.exec(meta.path)
    if (result[1]) {
      meta = {
        locale: result[1].replace('/', ''),
        path: result[2]
      }
    }
    return meta
  }
}
<|endoftext|>


<commit_before>"use strict";

class gswaMixer {
	constructor() {
		this._chans = {};
		this._fftSize = 128;
		this.audioDataL = new Uint8Array( 64 );
		this.audioDataR = new Uint8Array( 64 );
		this.data = this._proxInit();
	}

	setContext( ctx ) {
		const data = this.data,
			dataEnt = Object.entries( data );

		this.disconnect();
		this.ctx = ctx;
		if ( dataEnt.length ) {
			dataEnt.forEach( kv => delete data[ kv[ 0 ] ] );
			dataEnt.forEach( kv => data[ kv[ 0 ] ] = kv[ 1 ] );
		} else {
			data.main = {
				order: 0,
				toggle: true,
				name: "",
				pan: 0,
				gain: 1,
			};
		}
	}
	clear() {
		const d = this.data;

		Object.keys( d ).forEach( id => {
			if ( id !== "main" ) {
				delete d[ id ];
			}
		} );
		d.main.pan = 0;
		d.main.gain = 1;
		d.main.toggle = true;
	}
	connect( dest ) {
		this.disconnect();
		this._chans.main.output.connect( dest );
		this.connectedTo = dest;
	}
	disconnect() {
		if ( this._chans.main ) {
			this._chans.main.output.disconnect();
			this.connectedTo = null;
		}
	}
	getChanInput( id ) {
		return this._chans[ id ].input;
	}
	fillAudioData( chanId ) {
		const nodes = this._chans[ chanId ];

		nodes.analyserL.getByteFrequencyData( this.audioDataL );
		nodes.analyserR.getByteFrequencyData( this.audioDataR );
	}
	liveUpdateChan( chanId, prop, val ) {
		this._updateChan( chanId, prop, val );
	}

	// chan:
	_addChan( id ) {
		const ctx = this.ctx,
			pan = new gswaStereoPanner( ctx ),
			gain = ctx.createGain(),
			input = ctx.createGain(),
			output = ctx.createGain(),
			splitter = ctx.createChannelSplitter( 2 ),
			analyserL = ctx.createAnalyser(),
			analyserR = ctx.createAnalyser();

		analyserL.fftSize =
		analyserR.fftSize = this._fftSize;
		analyserL.smoothingTimeConstant =
		analyserR.smoothingTimeConstant = 0;
		input.connect( pan.getInput() );
		pan.connect( gain );
		gain.connect( output );
		gain.connect( splitter );
		splitter.connect( analyserL, 0 );
		splitter.connect( analyserR, 1 );
		this._chans[ id ] = {
			input, pan, gain, output, splitter, analyserL, analyserR,
			analyserData: new Uint8Array( analyserL.frequencyBinCount )
		};
	}
	_deleteChan( id ) {
		const nodes = this._chans[ id ];

		nodes.pan.disconnect();
		nodes.gain.disconnect();
		nodes.input.disconnect();
		nodes.output.disconnect();
		nodes.splitter.disconnect();
		delete this._chans[ id ];
	}
	_updateChan( id, prop, val ) {
		const nodes = this._chans[ id ],
			now = this.ctx.currentTime;

		switch ( prop ) {
			case "pan":
				nodes.pan.setValueAtTime( val, now );
				break;
			case "gain":
				nodes.gain.gain.setValueAtTime( val, now );
				break;
			case "toggle":
				nodes.gain.gain.setValueAtTime( val ? this.data[ id ].gain : 0, now );
				break;
			case "dest":
				nodes.output.disconnect();
				if ( val in this.data ) {
					nodes.output.connect( this._chans[ val ].input );
				}
				break;
		}
	}

	// proxy:
	_proxInit() {
		return new Proxy( {}, {
			set: this._proxAddChan.bind( this ),
			deleteProperty: this._proxDeleteChan.bind( this ),
		} );
	}
	_proxAddChan( tar, prop, val ) {
		this._proxDeleteChan( tar, prop );
		return this.__proxAddChan( tar, prop, val );
	}
	_proxDeleteChan( tar, prop ) {
		if ( prop in tar ) {
			this._deleteChan( prop );
			delete tar[ prop ];
		}
		return true;
	}
	__proxAddChan( tar, id, obj ) {
		const tarchan = {
				order: 0,
				toggle: true,
				name: "",
				pan: 0,
				gain: 0,
			},
			_ = id !== "main" ? ( tarchan.dest = "main" ) : null,
			updateChan = this._proxUpdateChan.bind( this, id ),
			chan = new Proxy( Object.seal( tarchan ), { set: updateChan } );

		tar[ id ] = chan;
		this._addChan( id );
		chan.pan = obj.pan;
		chan.gain = obj.gain;
		chan.name = obj.name;
		chan.toggle = obj.toggle;
		chan.order = obj.order;
		if ( obj.dest ) {
			chan.dest = obj.dest;
		}
		Object.values( this.data ).forEach( chan => {
			if ( chan.dest === id ) {
				chan.dest = id;
			}
		} );
		return true;
	}
	_proxUpdateChan( id, tar, prop, val ) {
		tar[ prop ] = val;
		this._updateChan( id, prop, val );
		return true;
	}
}
<commit_msg>gswaMixer: coding style<commit_after>"use strict";

class gswaMixer {
	constructor() {
		this._chans = {};
		this._fftSize = 128;
		this.audioDataL = new Uint8Array( 64 );
		this.audioDataR = new Uint8Array( 64 );
		this.data = this._proxInit();
	}

	setContext( ctx ) {
		const data = this.data,
			dataEnt = Object.entries( data );

		this.disconnect();
		this.ctx = ctx;
		if ( dataEnt.length ) {
			dataEnt.forEach( kv => delete data[ kv[ 0 ] ] );
			dataEnt.forEach( kv => data[ kv[ 0 ] ] = kv[ 1 ] );
		} else {
			data.main = {
				order: 0,
				toggle: true,
				name: "",
				pan: 0,
				gain: 1,
			};
		}
	}
	clear() {
		const d = this.data;

		Object.keys( d ).forEach( id => {
			if ( id !== "main" ) {
				delete d[ id ];
			}
		} );
		d.main.pan = 0;
		d.main.gain = 1;
		d.main.toggle = true;
	}
	connect( dest ) {
		this.disconnect();
		this._chans.main.output.connect( dest );
		this.connectedTo = dest;
	}
	disconnect() {
		if ( this._chans.main ) {
			this._chans.main.output.disconnect();
			this.connectedTo = null;
		}
	}
	getChanInput( id ) {
		return this._chans[ id ].input;
	}
	fillAudioData( chanId ) {
		const nodes = this._chans[ chanId ];

		nodes.analyserL.getByteFrequencyData( this.audioDataL );
		nodes.analyserR.getByteFrequencyData( this.audioDataR );
	}
	liveUpdateChan( chanId, prop, val ) {
		this._updateChan( chanId, prop, val );
	}

	// chan:
	_addChan( id ) {
		const ctx = this.ctx,
			pan = new gswaStereoPanner( ctx ),
			gain = ctx.createGain(),
			input = ctx.createGain(),
			output = ctx.createGain(),
			splitter = ctx.createChannelSplitter( 2 ),
			analyserL = ctx.createAnalyser(),
			analyserR = ctx.createAnalyser();

		analyserL.fftSize =
		analyserR.fftSize = this._fftSize;
		analyserL.smoothingTimeConstant =
		analyserR.smoothingTimeConstant = 0;
		input.connect( pan.getInput() );
		pan.connect( gain );
		gain.connect( output );
		gain.connect( splitter );
		splitter.connect( analyserL, 0 );
		splitter.connect( analyserR, 1 );
		this._chans[ id ] = {
			input, pan, gain, output, splitter, analyserL, analyserR,
			analyserData: new Uint8Array( analyserL.frequencyBinCount )
		};
	}
	_deleteChan( id ) {
		const nodes = this._chans[ id ];

		nodes.pan.disconnect();
		nodes.gain.disconnect();
		nodes.input.disconnect();
		nodes.output.disconnect();
		nodes.splitter.disconnect();
		delete this._chans[ id ];
	}
	_updateChan( id, prop, val ) {
		const nodes = this._chans[ id ],
			now = this.ctx.currentTime;

		switch ( prop ) {
			case "pan":
				nodes.pan.setValueAtTime( val, now );
				break;
			case "gain":
				nodes.gain.gain.setValueAtTime( val, now );
				break;
			case "toggle":
				nodes.gain.gain.setValueAtTime( val ? this.data[ id ].gain : 0, now );
				break;
			case "dest":
				nodes.output.disconnect();
				if ( val in this.data ) {
					nodes.output.connect( this._chans[ val ].input );
				}
				break;
		}
	}

	// proxy:
	_proxInit() {
		return new Proxy( {}, {
			set: this._proxAddChan.bind( this ),
			deleteProperty: this._proxDeleteChan.bind( this ),
		} );
	}
	_proxAddChan( tar, prop, val ) {
		this._proxDeleteChan( tar, prop );
		return this.__proxAddChan( tar, prop, val );
	}
	_proxDeleteChan( tar, prop ) {
		if ( prop in tar ) {
			this._deleteChan( prop );
			delete tar[ prop ];
		}
		return true;
	}
	_proxUpdateChan( id, tar, prop, val ) {
		tar[ prop ] = val;
		this._updateChan( id, prop, val );
		return true;
	}
	__proxNewChan( id ) {
		const ch = {
				toggle: true,
				order: 0,
				name: "",
				gain: 0,
				pan: 0,
			};

		if ( id !== "main" ) {
			ch.dest = "main";
		}
		return Object.seal( ch );
	}
	__proxAddChan( tar, id, obj ) {
		const updateChan = this._proxUpdateChan.bind( this, id ),
			chan = new Proxy( Object.seal( this.__proxNewChan( id ) ), { set: updateChan } );

		tar[ id ] = chan;
		this._addChan( id );
		chan.pan = obj.pan;
		chan.gain = obj.gain;
		chan.name = obj.name;
		chan.toggle = obj.toggle;
		chan.order = obj.order;
		if ( obj.dest ) {
			chan.dest = obj.dest;
		}
		Object.values( this.data ).forEach( chan => {
			if ( chan.dest === id ) {
				chan.dest = id;
			}
		} );
		return true;
	}
}
<|endoftext|>


<commit_before>// example is based on the numpy neural network tutorial featured here
// https://iamtrask.github.io/2015/07/12/basic-python-network/

var Matrix = require('../vectorious').Matrix;

function addScalar(matrix, scalar) {
	return Matrix.scale(Matrix.ones.apply(null, matrix.shape), scalar).add(matrix);
}

function mul(m1, m2) {
	var copy = Matrix.zeros.apply(null, m1.shape);
	m1.each(function each(val, i, j) {
		var result = val * m2.get(i, j);
		copy.set(i, j, result);
	});
	return copy;
}

function dot(m1, m2) {
	return Matrix.multiply(m1, m2);
}

function sigmoid(val) {
	return 1.0 / (1 + Math.exp(-val));
}

function map(matrix, fn) {
	var copy = Matrix.zeros.apply(null, matrix.shape);
	matrix.each(function each(val, i, j) {
		copy.set(i, j, fn(val));
	});
	return copy;
}

var X = new Matrix([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]);
var y = new Matrix([[0,1,1,0]]).transpose();

var syn0 = addScalar(Matrix.scale(Matrix.random(3, 4), 2), -1);
var syn1 = addScalar(Matrix.scale(Matrix.random(4, 1), 2), -1);

var l1, l2, l2_delta, l1_delta;

for (var i = 0; i < 60000; i++) {
	l1 = map(dot(X, syn0), sigmoid);
	l2 = map(dot(l1, syn1), sigmoid);
	l2_delta = mul(Matrix.subtract(y, l2), mul(l2, (Matrix.subtract(Matrix.ones.apply(null, l2.shape), l2))));
	l1_delta = mul(dot(l2_delta, syn1.transpose()), mul(l1 , Matrix.subtract(Matrix.ones.apply(null, l1.shape), l1)));
	syn1 = syn1.add(dot(l1.transpose(), l2_delta));
	syn0 = syn0.add(dot(X.transpose(), l1_delta));
}

// final trained neural network output!
// should be closed to [[0, 1, 1, 0]] transpose
console.log(l2.toArray());<commit_msg>tabs to spaces<commit_after>// example is based on the numpy neural network tutorial featured here
// https://iamtrask.github.io/2015/07/12/basic-python-network/

var Matrix = require('../vectorious').Matrix;

function addScalar(matrix, scalar) {
  return Matrix.scale(Matrix.ones.apply(null, matrix.shape), scalar).add(matrix);
}

function mul(m1, m2) {
  var copy = Matrix.zeros.apply(null, m1.shape);
  m1.each(function each(val, i, j) {
    var result = val * m2.get(i, j);
    copy.set(i, j, result);
  });
  return copy;
}

function dot(m1, m2) {
  return Matrix.multiply(m1, m2);
}

function sigmoid(val) {
  return 1.0 / (1 + Math.exp(-val));
}

function map(matrix, fn) {
  var copy = Matrix.zeros.apply(null, matrix.shape);
  matrix.each(function each(val, i, j) {
    copy.set(i, j, fn(val));
  });
  return copy;
}

var X = new Matrix([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]);
var y = new Matrix([[0,1,1,0]]).transpose();

var syn0 = addScalar(Matrix.scale(Matrix.random(3, 4), 2), -1);
var syn1 = addScalar(Matrix.scale(Matrix.random(4, 1), 2), -1);

var l1, l2, l2_delta, l1_delta;

for (var i = 0; i < 60000; i++) {
  l1 = map(dot(X, syn0), sigmoid);
  l2 = map(dot(l1, syn1), sigmoid);
  l2_delta = mul(Matrix.subtract(y, l2), mul(l2, (Matrix.subtract(Matrix.ones.apply(null, l2.shape), l2))));
  l1_delta = mul(dot(l2_delta, syn1.transpose()), mul(l1 , Matrix.subtract(Matrix.ones.apply(null, l1.shape), l1)));
  syn1 = syn1.add(dot(l1.transpose(), l2_delta));
  syn0 = syn0.add(dot(X.transpose(), l1_delta));
}

// final trained neural network output!
// should be closed to [[0, 1, 1, 0]] transpose
console.log(l2.toArray());<|endoftext|>


<commit_before>/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        District Yearly Profile Controllers
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
function districtProfilePage(req, res) {

  const profileYearList = ["Select", "2017/2018", "2018/2019", "2019/2020"];

  req.session.profileData = req.session.profileData || {};

  var profileData = {
    profileYearList : profileYearList,
    profileYear : req.session.profileData.profileYear ? req.session.profileData.profileYear : "2017/2018",
    whpProfile : req.session.profileData.whpProfile ? req.session.profileData.whpProfile : 1300,
    pscProfile : req.session.profileData.pscProfile ? req.session.profileData.pscProfile : 100,
    totalProvPlaces : req.session.profileData.totalProvPlaces ? req.session.profileData.totalProvPlaces : 1400,
    controlGpPlaces : req.session.profileData.controlGpPlaces ? req.session.profileData.controlGpPlaces : 140,
    totalPlaces : req.session.profileData.totalPlaces ? req.session.profileData.totalPlaces : 1540
  };

  if (req.session.user.role !== 'gatekeeper') {
    res.redirect('/latest/selection_tool');
  } else {
    res.locals.user = req.session.user;
    res.render('latest/whp-annual-profile', profileData);
  }
}

function districtProfileAction(req, res) {

  var totalProvPlaces = parseInt(req.body.whpProfile) + parseInt(req.body.pscProfile);
  var controlGpPlaces = Math.round(totalProvPlaces / 10);
  var totalPlaces = Math.round(totalProvPlaces + controlGpPlaces);
  var inputProfileData = {
    profileYear : req.body.profileYear,
    whpProfile : req.body.whpProfile,
    pscProfile : req.body.pscProfile,
    addPlaces : req.body.addPlaces,
    totalProvPlaces : totalProvPlaces,
    controlGpPlaces : controlGpPlaces,
    totalPlaces : totalPlaces
  };

  req.session.profileData = inputProfileData;
  res.redirect('/latest/gatekeeper/profile');
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        District Weekly Profile Controllers
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
function districtWeeklyProfilePage (req, res) {

  req.session.profileData = req.session.profileData || {};
  req.session.weeklyProfileData = req.session.weeklyProfileData || {};

  var whpFlatProfile = 0;
  var pscFlatProfile = 0;
  var fullYearProfileData = [];
  var weeklyProfileDataToRender = {};

  if (isFalsey(req.session.profileData) || Object.keys(req.session.profileData).length === 0) {
    whpFlatProfile = Math.round(1300 / 52);
    pscFlatProfile = Math.round(100 / 52);
  } else {
    whpFlatProfile = Math.round(req.session.profileData.whpProfile / 52);
    pscFlatProfile = Math.round(req.session.profileData.pscProfile / 52);

  };

  // Check if data already saved in fullYearProfileData. If so, use session.weeklyProfileData. If not, initialise session.weeklyProfileData.
  if (isFalsey(req.session.weeklyProfileData) || Object.keys(req.session.weeklyProfileData).length === 0 ) {
    fullYearProfileData = setUpInitialFullYearProfile(req, whpFlatProfile, pscFlatProfile);
  } else {
    fullYearProfileData = setUpFullYearProfileFromSessionData(req);
  };

  var weeklyProfileDataToRender = {
    totalPlaces : req.session.profileData.totalPlaces ? req.session.profileData.totalPlaces : 1540,
    district : req.session.profileData.district ? req.session.profileData.district : "Mercia",
    profileYear : req.session.profileData.profileYear ? req.session.profileData.profileYear : "2017/2018",
    whpProfile : req.session.profileData.whpProfile ? req.session.profileData.whpProfile : 1300,
    pscProfile : req.session.profileData.pscProfile ? req.session.profileData.pscProfile : 100,
    fullYearProfileData : fullYearProfileData
  };

  res.render('latest/whp-weekly-profile', weeklyProfileDataToRender);
}

function districtWeeklyProfileAction (req, res) {

  let fullYearProfileData = getFullYearProfileDataFromRequestBody(req);

  req.session.weeklyProfileData = fullYearProfileData;

  res.redirect('/latest/gatekeeper/weeklyProfile');

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        View Allocation Controllers
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

function viewAllocationsPage (req, res) {

  res.render('latest/whp-district-allocations');

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        Utilities
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

function setUpInitialFullYearProfile(req, whpFlatProfile, pscFlatProfile) {
  let i;
  let fullYearProfileData = [];
  var weekDate;
  if (req.session.profileData.profileYear === "2017/2018") {
    weekDate = new Date("2017-04-06");
  } else if (req.session.profileData.profileYear === "2018/2019") {
    weekDate = new Date("2018-04-06");
  } else {
    weekDate = new Date("2019-04-06");
  }

  var stringDate;
  var newDate;

  for (i = 0; i <= 51; i++) {
    let weekNum = i + 1;
    console.log("Weekdate is : ", weekDate);
    if (weekNum > 1) {
      newDate = weekDate.getDate() + 7;
      weekDate.setDate(newDate);
    }
    stringDate = weekDate.toDateString();
    let weekProfileData = {
      weekNum : weekNum,
      weekDate : stringDate,
      weekwhpProfile : whpFlatProfile,
      weekpscProfile : pscFlatProfile,
      weekwhpExtrasProfile : 0,
      weekpscExtrasProfile : 0
    };
    fullYearProfileData.push(weekProfileData);
  }

  return fullYearProfileData;
}

function setUpFullYearProfileFromSessionData(req) {
  let j;
  let fullYearProfileData = [];
  for (j = 0; j <= 51; j++) {
    let weekProfileData = {
      weekNum : req.session.weeklyProfileData[j].weekNum,
      weekDate : req.session.weeklyProfileData[j].weekDate,
      weekwhpProfile : req.session.weeklyProfileData[j].weekwhpProfile,
      weekpscProfile : req.session.weeklyProfileData[j].weekpscProfile,
      weekwhpExtrasProfile : req.session.weeklyProfileData[j].weekwhpExtrasProfile,
      weekpscExtrasProfile : req.session.weeklyProfileData[j].weekpscExtrasProfile
    };
    fullYearProfileData.push(weekProfileData);
  }
  return fullYearProfileData;
}

function getFullYearProfileDataFromRequestBody(req) {
  let k;
  let fullYearProfileData = [];
  for (k = 0; k <= 51; k++) {
    let weekNum = k + 1;
    let weekProfileData = {
      weekNum : weekNum,
      weekwhpProfile : req.body['week' + weekNum + 'whpProfile'],
      weekpscProfile : req.body['week' + weekNum + 'pscProfile'],
      weekwhpExtrasProfile : req.body['week' + weekNum + 'whpExtrasProfile'],
      weekpscExtrasProfile : req.body['week' + weekNum + 'pscExtrasProfile']
    }
    fullYearProfileData.push(weekProfileData);
  }
  return fullYearProfileData;
}

function calcPercentAndFloor (num, perNum) {
  return Math.floor(num * perNum / 100);
}

function calcPercentAndRound (num, perNum) {
  return Math.round(num * perNum / 100);
}

function isFalsey (testValue) {
  return (testValue === undefined || testValue == null || testValue.length <= 0) ? true : false;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        Module Exports
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

module.exports.districtProfilePage = districtProfilePage;
module.exports.districtProfileAction = districtProfileAction;
module.exports.districtWeeklyProfilePage= districtWeeklyProfilePage;
module.exports.districtWeeklyProfileAction= districtWeeklyProfileAction;
module.exports.viewAllocationsPage = viewAllocationsPage;
<commit_msg>Added utility tobuild dates that display in GDS format. Dates not yet pulling through when save changes to weekly profile<commit_after>/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        District Yearly Profile Controllers
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
function districtProfilePage(req, res) {

  const profileYearList = ["Select", "2017/2018", "2018/2019", "2019/2020"];

  req.session.profileData = req.session.profileData || {};

  var profileData = {
    profileYearList : profileYearList,
    profileYear : req.session.profileData.profileYear ? req.session.profileData.profileYear : "2017/2018",
    whpProfile : req.session.profileData.whpProfile ? req.session.profileData.whpProfile : 1300,
    pscProfile : req.session.profileData.pscProfile ? req.session.profileData.pscProfile : 100,
    totalProvPlaces : req.session.profileData.totalProvPlaces ? req.session.profileData.totalProvPlaces : 1400,
    controlGpPlaces : req.session.profileData.controlGpPlaces ? req.session.profileData.controlGpPlaces : 140,
    totalPlaces : req.session.profileData.totalPlaces ? req.session.profileData.totalPlaces : 1540
  };

  if (req.session.user.role !== 'gatekeeper') {
    res.redirect('/latest/selection_tool');
  } else {
    res.locals.user = req.session.user;
    res.render('latest/whp-annual-profile', profileData);
  }
}

function districtProfileAction(req, res) {

  var totalProvPlaces = parseInt(req.body.whpProfile) + parseInt(req.body.pscProfile);
  var controlGpPlaces = Math.round(totalProvPlaces / 10);
  var totalPlaces = Math.round(totalProvPlaces + controlGpPlaces);
  var inputProfileData = {
    profileYear : req.body.profileYear,
    whpProfile : req.body.whpProfile,
    pscProfile : req.body.pscProfile,
    addPlaces : req.body.addPlaces,
    totalProvPlaces : totalProvPlaces,
    controlGpPlaces : controlGpPlaces,
    totalPlaces : totalPlaces
  };

  req.session.profileData = inputProfileData;
  res.redirect('/latest/gatekeeper/profile');
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        District Weekly Profile Controllers
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
function districtWeeklyProfilePage (req, res) {

  req.session.profileData = req.session.profileData || {};
  req.session.weeklyProfileData = req.session.weeklyProfileData || {};

  var whpFlatProfile = 0;
  var pscFlatProfile = 0;
  var fullYearProfileData = [];
  var weeklyProfileDataToRender = {};

  if (isFalsey(req.session.profileData) || Object.keys(req.session.profileData).length === 0) {
    whpFlatProfile = Math.round(1300 / 52);
    pscFlatProfile = Math.round(100 / 52);
  } else {
    whpFlatProfile = Math.round(req.session.profileData.whpProfile / 52);
    pscFlatProfile = Math.round(req.session.profileData.pscProfile / 52);

  };

  // Check if data already saved in fullYearProfileData. If so, use session.weeklyProfileData. If not, initialise session.weeklyProfileData.
  if (isFalsey(req.session.weeklyProfileData) || Object.keys(req.session.weeklyProfileData).length === 0 ) {
    fullYearProfileData = setUpInitialFullYearProfile(req, whpFlatProfile, pscFlatProfile);
  } else {
    fullYearProfileData = setUpFullYearProfileFromSessionData(req);
  };

  var weeklyProfileDataToRender = {
    totalPlaces : req.session.profileData.totalPlaces ? req.session.profileData.totalPlaces : 1540,
    district : req.session.profileData.district ? req.session.profileData.district : "Mercia",
    profileYear : req.session.profileData.profileYear ? req.session.profileData.profileYear : "2017/2018",
    whpProfile : req.session.profileData.whpProfile ? req.session.profileData.whpProfile : 1300,
    pscProfile : req.session.profileData.pscProfile ? req.session.profileData.pscProfile : 100,
    fullYearProfileData : fullYearProfileData
  };

  res.render('latest/whp-weekly-profile', weeklyProfileDataToRender);
}

function districtWeeklyProfileAction (req, res) {

  let fullYearProfileData = getFullYearProfileDataFromRequestBody(req);

  req.session.weeklyProfileData = fullYearProfileData;

  res.redirect('/latest/gatekeeper/weeklyProfile');

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        View Allocation Controllers
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

function viewAllocationsPage (req, res) {

  res.render('latest/whp-district-allocations');

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        Utilities
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

function setUpInitialFullYearProfile(req, whpFlatProfile, pscFlatProfile) {
  let i;
  let fullYearProfileData = [];
  var weekDate;
  var stringDate;
  var newDate;
  var weekNum;

  if (req.session.profileData.profileYear === "2019/2020") {
    weekDate = new Date("2019-04-06");
  } else if (req.session.profileData.profileYear === "2018/2019") {
    weekDate = new Date("2018-04-06");
  } else {
    weekDate = new Date("2017-04-06");
  }

  for (i = 0; i <= 51; i++) {
    weekNum = i + 1;

    if (weekNum > 1) {
      newDate = weekDate.getDate() + 7;
      weekDate.setDate(newDate);
    }

    stringDate = formatDateForDisplay(weekDate);

    let weekProfileData = {
      weekNum : weekNum,
      weekDate : stringDate,
      weekwhpProfile : whpFlatProfile,
      weekpscProfile : pscFlatProfile,
      weekwhpExtrasProfile : 0,
      weekpscExtrasProfile : 0
    };
    fullYearProfileData.push(weekProfileData);
  }

  return fullYearProfileData;
}

function setUpFullYearProfileFromSessionData(req) {
  let j;
  let fullYearProfileData = [];
  for (j = 0; j <= 51; j++) {
    let weekProfileData = {
      weekNum : req.session.weeklyProfileData[j].weekNum,
      weekDate : req.session.weeklyProfileData[j].weekDate,
      weekwhpProfile : req.session.weeklyProfileData[j].weekwhpProfile,
      weekpscProfile : req.session.weeklyProfileData[j].weekpscProfile,
      weekwhpExtrasProfile : req.session.weeklyProfileData[j].weekwhpExtrasProfile,
      weekpscExtrasProfile : req.session.weeklyProfileData[j].weekpscExtrasProfile
    };
    fullYearProfileData.push(weekProfileData);
  }
  return fullYearProfileData;
}

function getFullYearProfileDataFromRequestBody(req) {
  let k;
  let fullYearProfileData = [];
  for (k = 0; k <= 51; k++) {
    let weekNum = k + 1;
    let weekProfileData = {
      weekNum : weekNum,
      weekwhpProfile : req.body['week' + weekNum + 'whpProfile'],
      weekpscProfile : req.body['week' + weekNum + 'pscProfile'],
      weekwhpExtrasProfile : req.body['week' + weekNum + 'whpExtrasProfile'],
      weekpscExtrasProfile : req.body['week' + weekNum + 'pscExtrasProfile']
    }
    fullYearProfileData.push(weekProfileData);
  }
  return fullYearProfileData;
}

function calcPercentAndFloor (num, perNum) {
  return Math.floor(num * perNum / 100);
}

function calcPercentAndRound (num, perNum) {
  return Math.round(num * perNum / 100);
}

function isFalsey (testValue) {
  return (testValue === undefined || testValue == null || testValue.length <= 0) ? true : false;
}

function formatDateForDisplay (unformattedDate) {
  var formattedDate;
  var dateDay;
  var dateMonth;
  var dateYear;

  var month = new Array();
  month[0] = "January";
  month[1] = "February";
  month[2] = "March";
  month[3] = "April";
  month[4] = "May";
  month[5] = "June";
  month[6] = "July";
  month[7] = "August";
  month[8] = "September";
  month[9] = "October";
  month[10] = "November";
  month[11] = "December";

  dateDay = unformattedDate.getDate();
  dateMonth = month[unformattedDate.getMonth()];
  dateYear = unformattedDate.getFullYear();

  formattedDate = dateDay + ' ' + dateMonth + ' ' + dateYear;
  return formattedDate;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 /*                                        Module Exports
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

module.exports.districtProfilePage = districtProfilePage;
module.exports.districtProfileAction = districtProfileAction;
module.exports.districtWeeklyProfilePage= districtWeeklyProfilePage;
module.exports.districtWeeklyProfileAction= districtWeeklyProfileAction;
module.exports.viewAllocationsPage = viewAllocationsPage;
<|endoftext|>


<commit_before>/*
 * grunt-natural-docs
 * https://github.com/Askelkana/natural_docs
 *
 * Copyright (c) 2013 askelkana
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function (grunt) {
	grunt.registerMultiTask('natural_docs', 'Builds NaturalDocs-based documentation.', function () {
		var options = this.options({
			bin:      '/usr/lib/bin/natural_docs',
			excludes: [],
			flags:    [ '-r', '-oft' ],
			format:   'HTML',
			inputs:   [],
			output:   '/docs',
			project:  '',
			projects: '/usr/lib/bin/natural_docs/projects',
			src:      '',
			styles:   [ 'Default' ]
		});
		var that = this;
		var done = this.async();
		var exec = require('child_process').exec;
		var settings = (function () {
			var settings = {};
			for (var i in options) {
				settings[i] = that.data.hasOwnProperty(i) ? that.data[i] : options[i];
			}
			return settings;
		})();
		var command = [ settings.bin ];
		addProject();
		addInputs();
		addExcludes();
		addOutput();
		addStyles();
		addFlags();
		exec(command.join(' '), function (error, stdout) {
			grunt.log.write(stdout);
			if (error !== null) {
				grunt.log.error('\n#' + command + '\n' + error);
			}
			done();
		});

		function addExcludes() {
			if (!(settings.excludes instanceof Array)) {
				grunt.log.error(that.name + ': illegal excludes.');
				return;
			}
			for (var i in settings.excludes) {
				command.push('-xi', complete(settings.src, settings.excludes[i]));
			}
		}

		function addInputs() {
			if (!(settings.inputs instanceof Array)) {
				grunt.log.error(that.name + ': illegal inputs.');
				return;
			}
			if (settings.inputs.length == 0) {
				grunt.log.error(that.name + ': no inputs specified.');
				return;
			}
			for (var i in settings.inputs) {
				command.push('-i', complete(settings.src, settings.inputs[i]));
			}
		}

		function addFlags() {
			if (!(settings.flags instanceof Array)) {
				grunt.log.error(that.name + ': illegal flags.');
				return;
			}
			command = command.concat(settings.flags);
		}

		function addOutput() {
			if (typeof settings.output != 'string') {
				grunt.log.error(that.name + ': illegal or missing output.');
				return;
			}
			command.push('-o', (settings.format), complete(settings.src, settings.output));
		}

		function addProject() {
			command.push('-p', complete(options.projects, settings.project));
		}

		function addStyles() {
			command.push('-s');
			command = command.concat(settings.styles);
		}

		function complete(root, addition) {
			if (root && addition.indexOf('/') == 0) {
				return root + addition;
			} else {
				return addition;
			}
		}
	});
};
<commit_msg>Error message improved/corrected<commit_after>/*
 * grunt-natural-docs
 * https://github.com/Askelkana/natural_docs
 *
 * Copyright (c) 2013 askelkana
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function (grunt) {
	grunt.registerMultiTask('natural_docs', 'Builds NaturalDocs-based documentation.', function () {
		var options = this.options({
			bin:      '/usr/lib/bin/natural_docs',
			excludes: [],
			flags:    [ '-r', '-oft' ],
			format:   'HTML',
			inputs:   [],
			output:   '/docs',
			project:  '',
			projects: '/usr/lib/bin/natural_docs/projects',
			src:      '',
			styles:   [ 'Default' ]
		});
		var that = this;
		var done = this.async();
		var exec = require('child_process').exec;
		var settings = (function () {
			var settings = {};
			for (var i in options) {
				settings[i] = that.data.hasOwnProperty(i) ? that.data[i] : options[i];
			}
			return settings;
		})();
		var command = [ settings.bin ];
		addProject();
		addInputs();
		addExcludes();
		addOutput();
		addStyles();
		addFlags();
		command = command.join(' ');
		exec(command, function (error, stdout) {
			grunt.log.write(stdout);
			if (error !== null) {
				grunt.log.error('\n#' + command + '\n' + error);
			}
			done();
		});

		function addExcludes() {
			if (!(settings.excludes instanceof Array)) {
				grunt.log.error(that.name + ': illegal excludes.');
				return;
			}
			for (var i in settings.excludes) {
				command.push('-xi', complete(settings.src, settings.excludes[i]));
			}
		}

		function addInputs() {
			if (!(settings.inputs instanceof Array)) {
				grunt.log.error(that.name + ': illegal inputs.');
				return;
			}
			if (settings.inputs.length == 0) {
				grunt.log.error(that.name + ': no inputs specified.');
				return;
			}
			for (var i in settings.inputs) {
				command.push('-i', complete(settings.src, settings.inputs[i]));
			}
		}

		function addFlags() {
			if (!(settings.flags instanceof Array)) {
				grunt.log.error(that.name + ': illegal flags.');
				return;
			}
			command = command.concat(settings.flags);
		}

		function addOutput() {
			if (typeof settings.output != 'string') {
				grunt.log.error(that.name + ': illegal or missing output.');
				return;
			}
			command.push('-o', (settings.format), complete(settings.src, settings.output));
		}

		function addProject() {
			command.push('-p', complete(options.projects, settings.project));
		}

		function addStyles() {
			command.push('-s');
			command = command.concat(settings.styles);
		}

		function complete(root, addition) {
			if (root && addition.indexOf('/') == 0) {
				return root + addition;
			} else {
				return addition;
			}
		}
	});
};
<|endoftext|>