<commit_before>'use strict';

/**
 * Requirements
 * @ignore
 */
const Base = require('../../Base.js').Base;
const BaseArray = require('../../base/BaseArray.js').BaseArray;

/**
 * @class
 * @memberOf export.ast
 * @extends {Base}
 */
class Node extends Base
{
    /**
     * @param {Object} values
     */
    constructor(values)
    {
        super();

        // Assign options
        const v = values || {};
        this._parent = v.parent || false;
        this._type = v.type || this.className.split('/').pop();
        this._dataFields = [];
        this._iterableFields = [];
    }

    /**
     * @inheritDoc
     */
    static get className()
    {
        return 'export.ast/Node';
    }

    /**
     * @property {Array}
     */
    get iterableFields()
    {
        return this._iterableFields;
    }

    set iterableFields(value)
    {
        this._iterableFields = value;
    }

    /**
     * @property {Array}
     */
    get dataFields()
    {
        return this._dataFields;
    }

    set dataFields(value)
    {
        this._dataFields = value;
    }

    /**
     * @property {String}
     */
    get type()
    {
        return this._type;
    }

    set type(value)
    {
        this._type = value;
    }

    /**
     * @property {export.ast.Node}
     */
    get parent()
    {
        return this._parent;
    }

    set parent(value)
    {
        this._parent = value;
    }

    /**
     * @property {export.ast.Node}
     */
    get next()
    {
        if (!this.parent || !this.parent.children)
        {
            return false;
        }
        const index = this.parent.children.indexOf(this);
        return this.parent.children[index + 1] || false;
    }

    /**
     * @property {export.ast.Node}
     */
    get previous()
    {
        if (!this.parent || !this.parent.children)
        {
            return false;
        }
        const index = this.parent.children.indexOf(this);
        return this.parent.children[index - 1] || false;
    }

    /**
     * Checks if node is of a specific type or has a specific property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {Bool}
     */
    is(type, properties)
    {
        // Check type
        if (type)
        {
            const types = Array.isArray(type) ? type : [type];
            if (types.indexOf(this.type) === -1)
            {
                return false;
            }
        }

        // Check properties
        if (properties)
        {
            for (const property in properties)
            {
                const query = properties[property];

                // Query for value
                if (typeof query === 'string' || Array.isArray(query))
                {
                    const values = Array.isArray(query) ? query : [query];
                    if (values.indexOf(this[property]) === -1)
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Checks if any of this nodes parents is of a specific type or has a specific property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {Bool}
     */
    isChildOf(type, properties)
    {
        if (!this.parent)
        {
            return false;
        }
        if (this.parent.is(type, properties))
        {
            return true;
        }
        return this.parent.isChildOf(type, properties);
    }

    /**
     * Finds the first nodes with a specific type or property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {export.ast.Node}
     */
    find(type, properties)
    {
        if (this.is(type, properties))
        {
            return this;
        }
        for (const iterableField of this.iterableFields)
        {
            if (this[iterableField] instanceof Node)
            {
                const found = this[iterableField].find(type, properties);
                if (found)
                {
                    return found;
                }
            }
            if (this[iterableField] instanceof BaseArray)
            {
                for (const node of this[iterableField])
                {
                    const found = node.find(type, properties);
                    if (found)
                    {
                        return found;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Filters nodes with a specific type or property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {Array}
     */
    filter(type, properties)
    {
        const result = [];
        if (this.is(type, properties))
        {
            result.push(this);
        }
        for (const iterableField of this.iterableFields)
        {
            if (this[iterableField] instanceof Node)
            {
                result.push(...this[iterableField].filter(type, properties));
            }
            if (this[iterableField] instanceof BaseArray)
            {
                for (const node of this[iterableField])
                {
                    result.push(...node.filter(type, properties));
                }
            }
        }
        return result;
    }

    /**
     * Recursively serializes all dataFields into a object.
     *
     * @return {Object}
     */
    serialize()
    {
        const result = { type: this.type };
        for (const field of this.dataFields)
        {
            if (Array.isArray(this[field]))
            {
                result[field] = [];
                for (const item of this[field])
                {
                    if (item instanceof Node)
                    {
                        result[field].push(item.serialize());
                    }
                    else
                    {
                        result[field].push(item);
                    }
                }
            }
            else if (this[field] instanceof Node)
            {
                result[field] = this[field].serialize();
            }
            else
            {
                result[field] = this[field];
            }
        }
        return result;
    }

    /**
     * Clone the current node.
     *
     * @return {export.ast/Node}
     */
    clone()
    {
        const result = new this.constructor();
        for (const field of this.dataFields)
        {
            if (Array.isArray(this[field]))
            {
                result[field] = new BaseArray();
                for (const item of this[field])
                {
                    if (item instanceof Node)
                    {
                        result[field].push(item.clone());
                    }
                    else
                    {
                        result[field].push(item);
                    }
                }
            }
            else if (this[field] instanceof Node)
            {
                result[field] = this[field].clone();
            }
            else
            {
                result[field] = this[field];
            }
        }
        result.dataFields = this.dataFields.slice(0);
        result.iterableFields = this.iterableFields.slice(0);
        return result;
    }
}

/**
 * Exports
 * @ignore
 */
module.exports.Node = Node;
<commit_msg>feat(export.ast.Node): adds peek method to find nodes before or after the current one<commit_after>'use strict';

/**
 * Requirements
 * @ignore
 */
const Base = require('../../Base.js').Base;
const BaseArray = require('../../base/BaseArray.js').BaseArray;

/**
 * @class
 * @memberOf export.ast
 * @extends {Base}
 */
class Node extends Base
{
    /**
     * @param {Object} values
     */
    constructor(values)
    {
        super();

        // Assign options
        const v = values || {};
        this._parent = v.parent || false;
        this._type = v.type || this.className.split('/').pop();
        this._dataFields = [];
        this._iterableFields = [];
    }

    /**
     * @inheritDoc
     */
    static get className()
    {
        return 'export.ast/Node';
    }

    /**
     * @property {Array}
     */
    get iterableFields()
    {
        return this._iterableFields;
    }

    set iterableFields(value)
    {
        this._iterableFields = value;
    }

    /**
     * @property {Array}
     */
    get dataFields()
    {
        return this._dataFields;
    }

    set dataFields(value)
    {
        this._dataFields = value;
    }

    /**
     * @property {String}
     */
    get type()
    {
        return this._type;
    }

    set type(value)
    {
        this._type = value;
    }

    /**
     * @property {export.ast.Node}
     */
    get parent()
    {
        return this._parent;
    }

    set parent(value)
    {
        this._parent = value;
    }

    /**
     * @property {export.ast.Node}
     */
    get next()
    {
        if (!this.parent || !this.parent.children)
        {
            return false;
        }
        const index = this.parent.children.indexOf(this);
        return this.parent.children[index + 1] || false;
    }

    /**
     * @property {export.ast.Node}
     */
    get previous()
    {
        if (!this.parent || !this.parent.children)
        {
            return false;
        }
        const index = this.parent.children.indexOf(this);
        return this.parent.children[index - 1] || false;
    }

    /**
     * Checks if node is of a specific type or has a specific property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {Bool}
     */
    is(type, properties)
    {
        // Check type
        if (type)
        {
            const types = Array.isArray(type) ? type : [type];
            if (types.indexOf(this.type) === -1)
            {
                return false;
            }
        }

        // Check properties
        if (properties)
        {
            for (const property in properties)
            {
                const query = properties[property];

                // Query for value
                if (typeof query === 'string' || Array.isArray(query))
                {
                    const values = Array.isArray(query) ? query : [query];
                    if (values.indexOf(this[property]) === -1)
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Checks if any of this nodes parents is of a specific type or has a specific property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {Bool}
     */
    isChildOf(type, properties)
    {
        if (!this.parent)
        {
            return false;
        }
        if (this.parent.is(type, properties))
        {
            return true;
        }
        return this.parent.isChildOf(type, properties);
    }

    /**
     * Returns the node at offset.
     * Optionaly checks type & properties
     *
     * @param {Number} offset
     * @return {export.ast.Node}
     */
    peek(offset, type, properties)
    {
        if (!offset)
        {
            return this;
        }
        if (!this.parent || !this.parent.children)
        {
            return false;
        }
        const index = this.parent.children.indexOf(this);
        const result = this.parent.children[index + offset] || false;
        if (result && !result.is(type, properties))
        {
            return false;
        }
        return result;
    }

    /**
     * Finds the first nodes with a specific type or property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {export.ast.Node}
     */
    find(type, properties)
    {
        if (this.is(type, properties))
        {
            return this;
        }
        for (const iterableField of this.iterableFields)
        {
            if (this[iterableField] instanceof Node)
            {
                const found = this[iterableField].find(type, properties);
                if (found)
                {
                    return found;
                }
            }
            if (this[iterableField] instanceof BaseArray)
            {
                for (const node of this[iterableField])
                {
                    const found = node.find(type, properties);
                    if (found)
                    {
                        return found;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Filters nodes with a specific type or property
     *
     * @param {String|Array} [type] - Node type
     * @param {Object} [properties] - Node properties
     * @return {Array}
     */
    filter(type, properties)
    {
        const result = [];
        if (this.is(type, properties))
        {
            result.push(this);
        }
        for (const iterableField of this.iterableFields)
        {
            if (this[iterableField] instanceof Node)
            {
                result.push(...this[iterableField].filter(type, properties));
            }
            if (this[iterableField] instanceof BaseArray)
            {
                for (const node of this[iterableField])
                {
                    result.push(...node.filter(type, properties));
                }
            }
        }
        return result;
    }

    /**
     * Recursively serializes all dataFields into a object.
     *
     * @return {Object}
     */
    serialize()
    {
        const result = { type: this.type };
        for (const field of this.dataFields)
        {
            if (Array.isArray(this[field]))
            {
                result[field] = [];
                for (const item of this[field])
                {
                    if (item instanceof Node)
                    {
                        result[field].push(item.serialize());
                    }
                    else
                    {
                        result[field].push(item);
                    }
                }
            }
            else if (this[field] instanceof Node)
            {
                result[field] = this[field].serialize();
            }
            else
            {
                result[field] = this[field];
            }
        }
        return result;
    }

    /**
     * Clone the current node.
     *
     * @return {export.ast/Node}
     */
    clone()
    {
        const result = new this.constructor();
        for (const field of this.dataFields)
        {
            if (Array.isArray(this[field]))
            {
                result[field] = new BaseArray();
                for (const item of this[field])
                {
                    if (item instanceof Node)
                    {
                        result[field].push(item.clone());
                    }
                    else
                    {
                        result[field].push(item);
                    }
                }
            }
            else if (this[field] instanceof Node)
            {
                result[field] = this[field].clone();
            }
            else
            {
                result[field] = this[field];
            }
        }
        result.dataFields = this.dataFields.slice(0);
        result.iterableFields = this.iterableFields.slice(0);
        return result;
    }
}

/**
 * Exports
 * @ignore
 */
module.exports.Node = Node;
<|endoftext|>


<commit_before>/**
 * # PlayerServer
 * Copyright(c) 2017 Stefano Balietti <ste@nodegame.org>
 * MIT Licensed
 *
 * GameServer for the players endpoint
 *
 * Inherits from `GameServer` and attaches special listeners.
 *
 * http://nodegame.org
 */

"use strict";

// ## Global scope

module.exports = PlayerServer;

var GameServer = require('./../GameServer');

var ngc = require('nodegame-client');

var PlayerList = ngc.PlayerList,
Player = ngc.Player,
J = ngc.JSUS;

var action = ngc.constants.action;

PlayerServer.prototype.__proto__ = GameServer.prototype;
PlayerServer.prototype.constructor = PlayerServer;

/**
 * ## PlayerServer constructor
 *
 * Creates a new instance of PlayerServer
 *
 * @param {object} options A configuration object
 */
function PlayerServer(options, channel) {
    GameServer.call(this, options, channel);

    // ### PlayerServer.ADMIN_SERVER
    // Flag to distinguish from Admin Server at execution time.
    this.ADMIN_SERVER = false;

    /**
     * ### PlayerServer.acm
     *
     * Action control management
     */
    this.acm = {};

    // #### PlayerServer.acm.notify
    // Configuration object governing message passing between players.
    // By default, players are *not* notified of the activities of other
    // players, such as (connection, disconnection, entering a new stage). By
    // changing the value of the properties of this object it is possible to
    // send out notifications of the following types:
    //
    // - onConnect: sends PCONNECT, and PDISCONNECT
    // - onStageUpdate: sends PLAYER_UPDATE (player enters a new stage / step)
    // - onStageLevelUpdate: sends PLAYER_UPDATE (player update the state within
    //     a stage: from LOADING to DONE)
    this.acm.notify = this.options.notify || {};

    // #### PlayerServer.acm.forwardAll
    // If TRUE all DATA and TXT messages (regardless to the recipient)
    // will be forwarded to the admins. Default: FALSE
    this.acm.forwardAll = this.options.forwardAllMessages || false;

    // #### PlayerServer.acm.getFromAdmins
    // If TRUE players will be able to send GET messages to admins and get
    // back the result. Default: FALSE
    this.acm.getFromAdmins = this.options.getFromAdmins || false;
}

// ## PlayerServer methods

/**
 * ### PlayerServer.attachCustomListeners
 *
 * Implements `GameServer.attachCustomListeners`
 */
PlayerServer.prototype.attachCustomListeners = function() {
    var that = this,
    sys = this.sysLogger,
    say = action.SAY + '.',
    set = action.SET + '.',
    get = action.GET + '.';

    // #### connecting
    // Listens on newly connected players
    this.on('connecting', function(clientObj, roomName) {
        var room;
        room = that.channel.gameRooms[roomName];
        that.notifyRoomConnection(clientObj, room);
    });

    // #### re-connecting
    // TODO: cleanup code, maybe merge with connecting.
    this.on('re-connecting', function(clientObj, roomName) {
        var room, admins, clientId;
        var pConnectMsg;

        clientId = clientObj.id;
        room = that.channel.gameRooms[roomName];
        admins = room.clients.admin;

        pConnectMsg = that.msg.create({
            target: 'PRECONNECT',
            data:   clientObj,
            from:   clientId
        });

        // Unlike connect msgs, reconnect msgs are sent always just to admins.
        // Admins decides what to do with it.

        // Notifies the monitors that a new player connected.
        that.socketManager.send2group(pConnectMsg, admins);
    });

    // #### say.LOG
    // Listens on say.LOG messages.
    this.on(say + 'LOG', this.standardPlayerMsg);

   // #### say.DATA
    // Listens on say.DATA messages.
    this.on(say + 'DATA', this.standardPlayerMsg);

   // #### say.LANG
    // Listens on set.DATA messages
    this.on(say + 'LANG', function(msg) {
        var room, roomName;

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];

        if (!room) {
            sys.log('PlayerServer.on.say.LANG: Could not determine room of ' +
                    'sender: ' + msg.from, 'error');
            return;
        }

        if (!msg.data || 'string' !== typeof msg.data.name ||
            msg.data.name.trim() === '') {

            sys.log('PlayerServer.on.say.LANG: Wrong/Missing languange ' +
                    'information. Found: ' + msg.data, 'error');
            return;
        }

        // Update info in registry.
        that.channel.registry.updateClient(msg.from, { lang: msg.data });

        that.socketManager.send2roomAdmins(msg, room);
    });

    // #### set.DATA
    // Listens on set.DATA messages
    this.on(set + 'DATA', function(msg) {
        var room, roomName;

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];

        if (!room) {
            sys.log('PlayerServer.on.set.DATA: Could not determine room of ' +
                    'sender: ' + msg.from, 'error');
            return;
        }

        that.socketManager.send2roomAdmins(msg, room);
    });

    // #### say.PLAYER_UPDATE
    // Listens on say.PLAYER_UPDATE messages
    this.on(say + 'PLAYER_UPDATE', function(msg) {
        var room, roomName;

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];
        if (!room) {
            sys.log('PlayerServer.on.say.PLAYER_UPDATE: Could not ' +
                    'determine room of sender: ' + msg.from, 'error');
            return;
        }

        // StateLevel is never passed to other players.
        if ((room.acm.notify.onStageUpdate && msg.text === 'stage') ||
            (msg.text === 'stageLevel' && (room.acm.notify.onStageLevelUpdate ||
              (room.acm.notify.onStageLoadedUpdate &&
               msg.data.stageLevel === ngc.constants.stageLevels.LOADED)))) {

            that.socketManager.broadcast2room(msg, room, msg.from);
        }
        else {

            that.socketManager.send2roomAdmins(msg, room);
        }
    });

    // #### get.DATA
    // Listener on get.
    // It ignores ALL, CHANNEL, CHANNEL_X, ROOM_X recipients.
    this.on(get + 'DATA', function(msg) {
        var room, roomName;
        var players, oldPlayers;

        if (J.isEmpty(msg.text)) {
            sys.log('PlayerServer.on.get.DATA: discarded invalid msg without ' +
                    'label.', 'error');
            return;
        }

        if (msg.text.split('.') > 1) {
            sys.log('PlayerServer.on.get.DATA: discarded invalid msg with ' +
                    '"." character in the label.', 'error');
            return;
        }

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];

        if (!room) {
            sys.log('PlayerServer.on.get.DATA: Could not determine ' +
                    'room of sender: ' + msg.from, 'error');
            return;
        }

        if (msg.to === 'ROOM') {
            if (room.acm.getFromAdmins) {
                that.socketManager.broadcast2room(msg, room, msg.from);
            }
            else {

                players = room.clients.players;
                oldPlayers = players.breed();
                players.id.remove(msg.from);

                that.socketManager.send2group(msg, oldPlayers);
            }
        }

        else if (msg.to === 'SERVER') {

            if (!room.acm.getFromAdmins) return;

            that.socketManager.send2roomAdmins(msg, room);
        }

        else {
            // Msg to a specific client (might be in another room).
            that.socketManager.send2client(msg);
        }

    });

    // #### disconnect
    // Listens on players disconnecting
    this.on('disconnect', function(player, room) {
        var playerName;
        playerName = player.id + ' (sid: ' + player.sid + ')';
        sys.log(that.name + ' client disconnected: ' + playerName);

        that.notifyRoomDisconnection(player, room);
    });

    // #### shutdown
    // Listens on server shutdown
    this.sio.sockets.on('shutdown', function(message) {
        sys.log('Player server ' + that.name + ' is shutting down');
        // TODO save the state to disk.
    });
};

/**
 * ### PlayerServer.standardPlayerMsg
 *
 * Handles incoming messages to the Player Server
 *
 * Evaluates the _to_ field and calls the right methods from the socket manager.
 *
 * It is used for the following targets: DATA, TXT.
 *
 * Note: it still possible to send messages to admin by specifying the direct
 * the client-id of the recipient.
 *
 * @param {GameMsg} msg The incoming game message
 *
 * @see SocketManager
 */
PlayerServer.prototype.standardPlayerMsg = function(msg) {
    var roomName, room, channel;

    if (msg.to === 'ALL') {
        this.socketManager.broadcast2all(msg);
    }
    else if (msg.to === 'CHANNEL') {
        this.socketManager.broadcast2ownChannel(msg);
    }
    else if (msg.to === 'ROOM') {
        this.socketManager.broadcast2ownRoom(msg);
    }
    else if (msg.to === 'CHANNEL_X') {
        this.socketManager.broadcast2channel(msg, msg._to);
    }
    else if (msg.to === 'ROOM_X') {
        this.socketManager.broadcast2room(msg, msg._to);
    }
    else {
        roomName = msg._roomName || this.registry.getClientRoom(msg.from);
        room = this.channel.gameRooms[roomName];
        if (!room) {
            sys.log('PlayerServer.on.' + msg.toEvent() + ': Could not ' +
                    'determine room of msg sender: ' + msg.from, 'error');
            return;
        }

        // TODO: improve. Too many ifs.

        if (msg.to === 'SERVER' || room.acm.forwardAll) {
            // Send to admins in sender's room.
            this.socketManager.send2roomAdmins(msg, room);
        }

        if (msg.to !== 'SERVER') {
            // Send to receiving player (do not send twice to admin.)
            if (!room.acm.forwardAll ||
                !this.channel.registry.clients.admin.get(msg.to)) {

                this.socketManager.send2client(msg);
            }
        }
    }
};

/**
 * ## PlayerServer.notifyRoomConnection
 *
 * Implements GameServer.notifyRoomConnection
 */
PlayerServer.prototype.notifyRoomConnection = function(clientObj, roomObj) {
    var players, admins, oldPlayers, filteredPlayers;
    var pConnectMsg, clientId;

    clientId = clientObj.id;

    pConnectMsg = this.msg.create({
        target: 'PCONNECT',
        from:    clientId
    });

    players = roomObj.clients.player;
    admins = roomObj.clients.admin;

    // Send notification to other players (if allowed).
    if (roomObj.acm.notify.onConnect && players.db.length > 1) {

        // To players send only the id.
        pConnectMsg.data = { id: clientId };

        oldPlayers = players.breed();
        oldPlayers.id.remove(clientId);

        this.socketManager.broadcast2group(pConnectMsg, oldPlayers);

        // Send the current player list to the new player.
        filteredPlayers = oldPlayers.fetchSubObj('id');
        this.socketManager.send2client(this.msg.create({
            target: 'SETUP',
            to:     clientId,
            text:   'plist',
            data:   J.stringify([filteredPlayers, 'replace'])
        }));
    }

    // To admins send the full object.
    pConnectMsg.data = clientObj;
    // Send notification to admin (always).
    this.socketManager.send2group(pConnectMsg, admins);
};

/**
 * ## PlayerServer.notifyRoomDisconnection
 *
 * Implements GameServer.notifyRoomDisconnection
 */
PlayerServer.prototype.notifyRoomDisconnection = function(clientObj, roomObj) {
    var pDisconnectMsg, clientId;
    clientId = clientObj.id;
    pDisconnectMsg = this.msg.create({
        target: 'PDISCONNECT',
        data: { id: clientId, sid: clientObj.sid }
    });

    // Notify players and admins of disconnection in old room.
    if (roomObj.acm.notify.onConnect) {
        this.socketManager.broadcast2room(pDisconnectMsg, roomObj, clientId);
    }
    // Notify only admins of disconnection in old room.
    else {
        this.socketManager.send2roomAdmins(pDisconnectMsg, roomObj);
    }
};

// ## Old code.

    // #### say.STAGE
    // Listens on say.STAGE messages
//     this.on(say + 'STAGE', function(msg) {
//         // TODO: should registry's Player object get updated?
//         var room, roomName;
//
//         roomName = msg._roomName || that.registry.getClientRoom(msg.from);
//         room = that.channel.gameRooms[roomName];
//
//         if (!room) {
//             sys.log('PlayerServer.on.say.STAGE: Could not ' +
//                     'determine room of sender: ' + msg.from, 'error');
//             return;
//         }
//
//         if (room.acm.notify.onStageUpdate) {
//             that.socketManager.broadcast2room(msg, room, msg.from);
//         }
//         else {
//             that.socketManager.send2roomAdmins(msg, room);
//         }
//     });

    // #### say.STAGE_LEVEL
    // Listens on say.STAGE_LEVEL messages
//     this.on(say + 'STAGE_LEVEL', function(msg) {
//         // TODO: should registry's Player object get updated?
//         var room, roomName;
//
//         roomName = msg._roomName || that.registry.getClientRoom(msg.from);
//         room = that.channel.gameRooms[roomName];
//
//         if (!room) {
//             sys.log('PlayerServer.on.say.STAGE_LEVEL: Could not ' +
//                     'determine room of sender: ' + msg.from, 'error');
//             return;
//         }
//
//         if (room.acm.notify.onStageUpdate) {
//             that.socketManager.broadcast2room(msg, room, msg.from);
//         }
//         else {
//             that.socketManager.send2roomAdmins(msg, room);
//         }
//     });
<commit_msg>roomDisconnect sends more info<commit_after>/**
 * # PlayerServer
 * Copyright(c) 2017 Stefano Balietti <ste@nodegame.org>
 * MIT Licensed
 *
 * GameServer for the players endpoint
 *
 * Inherits from `GameServer` and attaches special listeners.
 *
 * http://nodegame.org
 */

"use strict";

// ## Global scope

module.exports = PlayerServer;

var GameServer = require('./../GameServer');

var ngc = require('nodegame-client');

var PlayerList = ngc.PlayerList,
Player = ngc.Player,
J = ngc.JSUS;

var action = ngc.constants.action;

PlayerServer.prototype.__proto__ = GameServer.prototype;
PlayerServer.prototype.constructor = PlayerServer;

/**
 * ## PlayerServer constructor
 *
 * Creates a new instance of PlayerServer
 *
 * @param {object} options A configuration object
 */
function PlayerServer(options, channel) {
    GameServer.call(this, options, channel);

    // ### PlayerServer.ADMIN_SERVER
    // Flag to distinguish from Admin Server at execution time.
    this.ADMIN_SERVER = false;

    /**
     * ### PlayerServer.acm
     *
     * Action control management
     */
    this.acm = {};

    // #### PlayerServer.acm.notify
    // Configuration object governing message passing between players.
    // By default, players are *not* notified of the activities of other
    // players, such as (connection, disconnection, entering a new stage). By
    // changing the value of the properties of this object it is possible to
    // send out notifications of the following types:
    //
    // - onConnect: sends PCONNECT, and PDISCONNECT
    // - onStageUpdate: sends PLAYER_UPDATE (player enters a new stage / step)
    // - onStageLevelUpdate: sends PLAYER_UPDATE (player update the state within
    //     a stage: from LOADING to DONE)
    this.acm.notify = this.options.notify || {};

    // #### PlayerServer.acm.forwardAll
    // If TRUE all DATA and TXT messages (regardless to the recipient)
    // will be forwarded to the admins. Default: FALSE
    this.acm.forwardAll = this.options.forwardAllMessages || false;

    // #### PlayerServer.acm.getFromAdmins
    // If TRUE players will be able to send GET messages to admins and get
    // back the result. Default: FALSE
    this.acm.getFromAdmins = this.options.getFromAdmins || false;
}

// ## PlayerServer methods

/**
 * ### PlayerServer.attachCustomListeners
 *
 * Implements `GameServer.attachCustomListeners`
 */
PlayerServer.prototype.attachCustomListeners = function() {
    var that = this,
    sys = this.sysLogger,
    say = action.SAY + '.',
    set = action.SET + '.',
    get = action.GET + '.';

    // #### connecting
    // Listens on newly connected players
    this.on('connecting', function(clientObj, roomName) {
        var room;
        room = that.channel.gameRooms[roomName];
        that.notifyRoomConnection(clientObj, room);
    });

    // #### re-connecting
    // TODO: cleanup code, maybe merge with connecting.
    this.on('re-connecting', function(clientObj, roomName) {
        var room, admins, clientId;
        var pConnectMsg;

        clientId = clientObj.id;
        room = that.channel.gameRooms[roomName];
        admins = room.clients.admin;

        pConnectMsg = that.msg.create({
            target: 'PRECONNECT',
            data:   clientObj,
            from:   clientId
        });

        // Unlike connect msgs, reconnect msgs are sent always just to admins.
        // Admins decides what to do with it.

        // Notifies the monitors that a new player connected.
        that.socketManager.send2group(pConnectMsg, admins);
    });

    // #### say.LOG
    // Listens on say.LOG messages.
    this.on(say + 'LOG', this.standardPlayerMsg);

   // #### say.DATA
    // Listens on say.DATA messages.
    this.on(say + 'DATA', this.standardPlayerMsg);

   // #### say.LANG
    // Listens on set.DATA messages
    this.on(say + 'LANG', function(msg) {
        var room, roomName;

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];

        if (!room) {
            sys.log('PlayerServer.on.say.LANG: Could not determine room of ' +
                    'sender: ' + msg.from, 'error');
            return;
        }

        if (!msg.data || 'string' !== typeof msg.data.name ||
            msg.data.name.trim() === '') {

            sys.log('PlayerServer.on.say.LANG: Wrong/Missing languange ' +
                    'information. Found: ' + msg.data, 'error');
            return;
        }

        // Update info in registry.
        that.channel.registry.updateClient(msg.from, { lang: msg.data });

        that.socketManager.send2roomAdmins(msg, room);
    });

    // #### set.DATA
    // Listens on set.DATA messages
    this.on(set + 'DATA', function(msg) {
        var room, roomName;

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];

        if (!room) {
            sys.log('PlayerServer.on.set.DATA: Could not determine room of ' +
                    'sender: ' + msg.from, 'error');
            return;
        }

        that.socketManager.send2roomAdmins(msg, room);
    });

    // #### say.PLAYER_UPDATE
    // Listens on say.PLAYER_UPDATE messages
    this.on(say + 'PLAYER_UPDATE', function(msg) {
        var room, roomName;

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];
        if (!room) {
            sys.log('PlayerServer.on.say.PLAYER_UPDATE: Could not ' +
                    'determine room of sender: ' + msg.from, 'error');
            return;
        }

        // StateLevel is never passed to other players.
        if ((room.acm.notify.onStageUpdate && msg.text === 'stage') ||
            (msg.text === 'stageLevel' && (room.acm.notify.onStageLevelUpdate ||
              (room.acm.notify.onStageLoadedUpdate &&
               msg.data.stageLevel === ngc.constants.stageLevels.LOADED)))) {

            that.socketManager.broadcast2room(msg, room, msg.from);
        }
        else {

            that.socketManager.send2roomAdmins(msg, room);
        }
    });

    // #### get.DATA
    // Listener on get.
    // It ignores ALL, CHANNEL, CHANNEL_X, ROOM_X recipients.
    this.on(get + 'DATA', function(msg) {
        var room, roomName;
        var players, oldPlayers;

        if (J.isEmpty(msg.text)) {
            sys.log('PlayerServer.on.get.DATA: discarded invalid msg without ' +
                    'label.', 'error');
            return;
        }

        if (msg.text.split('.') > 1) {
            sys.log('PlayerServer.on.get.DATA: discarded invalid msg with ' +
                    '"." character in the label.', 'error');
            return;
        }

        roomName = msg._roomName || that.registry.getClientRoom(msg.from);
        room = that.channel.gameRooms[roomName];

        if (!room) {
            sys.log('PlayerServer.on.get.DATA: Could not determine ' +
                    'room of sender: ' + msg.from, 'error');
            return;
        }

        if (msg.to === 'ROOM') {
            if (room.acm.getFromAdmins) {
                that.socketManager.broadcast2room(msg, room, msg.from);
            }
            else {

                players = room.clients.players;
                oldPlayers = players.breed();
                players.id.remove(msg.from);

                that.socketManager.send2group(msg, oldPlayers);
            }
        }

        else if (msg.to === 'SERVER') {

            if (!room.acm.getFromAdmins) return;

            that.socketManager.send2roomAdmins(msg, room);
        }

        else {
            // Msg to a specific client (might be in another room).
            that.socketManager.send2client(msg);
        }

    });

    // #### disconnect
    // Listens on players disconnecting
    this.on('disconnect', function(player, room) {
        var playerName;
        playerName = player.id + ' (sid: ' + player.sid + ')';
        sys.log(that.name + ' client disconnected: ' + playerName);

        that.notifyRoomDisconnection(player, room);
    });

    // #### shutdown
    // Listens on server shutdown
    this.sio.sockets.on('shutdown', function(message) {
        sys.log('Player server ' + that.name + ' is shutting down');
        // TODO save the state to disk.
    });
};

/**
 * ### PlayerServer.standardPlayerMsg
 *
 * Handles incoming messages to the Player Server
 *
 * Evaluates the _to_ field and calls the right methods from the socket manager.
 *
 * It is used for the following targets: DATA, TXT.
 *
 * Note: it still possible to send messages to admin by specifying the direct
 * the client-id of the recipient.
 *
 * @param {GameMsg} msg The incoming game message
 *
 * @see SocketManager
 */
PlayerServer.prototype.standardPlayerMsg = function(msg) {
    var roomName, room, channel;

    if (msg.to === 'ALL') {
        this.socketManager.broadcast2all(msg);
    }
    else if (msg.to === 'CHANNEL') {
        this.socketManager.broadcast2ownChannel(msg);
    }
    else if (msg.to === 'ROOM') {
        this.socketManager.broadcast2ownRoom(msg);
    }
    else if (msg.to === 'CHANNEL_X') {
        this.socketManager.broadcast2channel(msg, msg._to);
    }
    else if (msg.to === 'ROOM_X') {
        this.socketManager.broadcast2room(msg, msg._to);
    }
    else {
        roomName = msg._roomName || this.registry.getClientRoom(msg.from);
        room = this.channel.gameRooms[roomName];
        if (!room) {
            sys.log('PlayerServer.on.' + msg.toEvent() + ': Could not ' +
                    'determine room of msg sender: ' + msg.from, 'error');
            return;
        }

        // TODO: improve. Too many ifs.

        if (msg.to === 'SERVER' || room.acm.forwardAll) {
            // Send to admins in sender's room.
            this.socketManager.send2roomAdmins(msg, room);
        }

        if (msg.to !== 'SERVER') {
            // Send to receiving player (do not send twice to admin.)
            if (!room.acm.forwardAll ||
                !this.channel.registry.clients.admin.get(msg.to)) {

                this.socketManager.send2client(msg);
            }
        }
    }
};

/**
 * ## PlayerServer.notifyRoomConnection
 *
 * Implements GameServer.notifyRoomConnection
 */
PlayerServer.prototype.notifyRoomConnection = function(clientObj, roomObj) {
    var players, admins, oldPlayers, filteredPlayers;
    var pConnectMsg, clientId;

    clientId = clientObj.id;

    pConnectMsg = this.msg.create({
        target: 'PCONNECT',
        from:    clientId
    });

    players = roomObj.clients.player;
    admins = roomObj.clients.admin;

    // Send notification to other players (if allowed).
    if (roomObj.acm.notify.onConnect && players.db.length > 1) {

        // To players send only the id.
        pConnectMsg.data = { id: clientId };

        oldPlayers = players.breed();
        oldPlayers.id.remove(clientId);

        this.socketManager.broadcast2group(pConnectMsg, oldPlayers);

        // Send the current player list to the new player.
        filteredPlayers = oldPlayers.fetchSubObj('id');
        this.socketManager.send2client(this.msg.create({
            target: 'SETUP',
            to:     clientId,
            text:   'plist',
            data:   J.stringify([filteredPlayers, 'replace'])
        }));
    }

    // To admins send the full object.
    pConnectMsg.data = clientObj;
    // Send notification to admin (always).
    this.socketManager.send2group(pConnectMsg, admins);
};

/**
 * ## PlayerServer.notifyRoomDisconnection
 *
 * Implements GameServer.notifyRoomDisconnection
 */
PlayerServer.prototype.notifyRoomDisconnection = function(clientObj, roomObj) {
    var pDisconnectMsg;

    pDisconnectMsg = this.msg.create({
        target: 'PDISCONNECT',
        data: { id: clientObj.id, sid: clientObj.sid }
    });

    // Notify players of disconnection in old room.
    if (roomObj.acm.notify.onConnect) {
        this.socketManager.send2roomPlayers(pDisconnectMsg, roomObj);
    }
    // Notify only admins of disconnection in old room.
    pDisconnectMsg = this.msg.create({
        target: 'PDISCONNECT',
        data: clientObj
    });
    this.socketManager.send2roomAdmins(pDisconnectMsg, roomObj);
};

// ## Old code.

    // #### say.STAGE
    // Listens on say.STAGE messages
//     this.on(say + 'STAGE', function(msg) {
//         // TODO: should registry's Player object get updated?
//         var room, roomName;
//
//         roomName = msg._roomName || that.registry.getClientRoom(msg.from);
//         room = that.channel.gameRooms[roomName];
//
//         if (!room) {
//             sys.log('PlayerServer.on.say.STAGE: Could not ' +
//                     'determine room of sender: ' + msg.from, 'error');
//             return;
//         }
//
//         if (room.acm.notify.onStageUpdate) {
//             that.socketManager.broadcast2room(msg, room, msg.from);
//         }
//         else {
//             that.socketManager.send2roomAdmins(msg, room);
//         }
//     });

    // #### say.STAGE_LEVEL
    // Listens on say.STAGE_LEVEL messages
//     this.on(say + 'STAGE_LEVEL', function(msg) {
//         // TODO: should registry's Player object get updated?
//         var room, roomName;
//
//         roomName = msg._roomName || that.registry.getClientRoom(msg.from);
//         room = that.channel.gameRooms[roomName];
//
//         if (!room) {
//             sys.log('PlayerServer.on.say.STAGE_LEVEL: Could not ' +
//                     'determine room of sender: ' + msg.from, 'error');
//             return;
//         }
//
//         if (room.acm.notify.onStageUpdate) {
//             that.socketManager.broadcast2room(msg, room, msg.from);
//         }
//         else {
//             that.socketManager.send2roomAdmins(msg, room);
//         }
//     });
<|endoftext|>


<commit_before><commit_msg>resubmitting the code in new branch<commit_after><|endoftext|>


<commit_before>/**
 * bootbox.js [v4.x/master branch]
 *
 * http://bootboxjs.com/license.txt
 */
// @see https://github.com/makeusabrew/bootbox/issues/71
window.bootbox = window.bootbox || (function(document, $, undefined) {
  "use strict";

  // the base DOM structure needed to create a modal
  var templates = {
    dialog:
      "
" +
        "
" +
          "
" +
            "

" +
          "
" +
        "
" +
      "
",
    header:
      "
" +
        "

" +
      "
",
    footer:
      "

",
    closeButton:
      "<button type='button' class='bootbox-close-button close'>×</button>",
    form:
      "<form class='bootbox-form'></form>",
    inputs: {
      text:
        "<input class='bootbox-input input-block-level' autocomplete=off type=text />"
    }
  };

  // cache a reference to the jQueryfied body element
  var appendTo = $("body");

  var defaults = {
    // default language
    locale: "en",
    // show backdrop or not
    backdrop: true,
    // animate the modal in/out
    animate: true,
    // additional class string applied to the top level dialog
    className: null,
    // show the modal header or not
    header: false,
    // whether or not to include a close button, if a header is present
    closeButton: true,
    // show the dialog immediately by default
    show: true
  };

  // our public object; augmented after our private API
  var exports = {};

  /**
   * @private
   */
  function _t(key) {
    return locales[defaults.locale][key] || locales.en[key];
  }

  function processCallback(e, dialog, callback) {
    e.preventDefault();

    // by default we assume a callback will get rid of the dialog,
    // although it is given the opportunity to override this

    // so, if the callback can be invoked and it *explicitly returns false*
    // then we'll set a flag to keep the dialog active...
    var preserveDialog = $.isFunction(callback) && callback(e) === false;

    // ... otherwise we'll bin it
    if (!preserveDialog) {
      dialog.modal("hide");
    }
  }

  function getKeyLength(obj) {
    // @TODO defer to Object.keys(x).length if available?
    var k, t = 0;
    for (k in obj) {
      t ++;
    }
    return t;
  }

  function each(collection, iterator) {
    var index = 0;
    $.each(collection, function(key, value) {
      iterator(key, value, index++);
    });
  }

  function sanitize(options) {
    var buttons;
    var total;

    if (typeof options !== "object") {
      throw new Error("Please supply an object of options");
    }

    if (!options.message) {
      throw new Error("Please specify a message");
    }

    // make sure any supplied options take precedence over defaults
    options = $.extend({}, defaults, options);

    if (!options.buttons) {
      options.buttons = {};
    }

    // we only support Bootstrap's "static" and false backdrop args
    // supporting true would mean you could dismiss the dialog without
    // explicitly interacting with it
    options.backdrop = options.backdrop ? "static" : false;

    buttons = options.buttons;

    total = getKeyLength(buttons);

    each(buttons, function(key, button, index) {

      if (!button.label) {
        throw new Error("button with key " + key + " requires a label");
      }

      if (!button.className) {
        if (total <= 2 && index === total-1) {
          // always add a primary to the main option in a two-button dialog
          button.className = "btn-primary";
        } else {
          button.className = "btn-default";
        }
      }
    });

    return options;
  }

  function mapArguments(args, properties) {
    var argn = args.length;
    var options = {};

    if (argn < 1 || argn > 2) {
      throw new Error("Invalid argument length");
    }

    if (argn === 2 || typeof args[0] === "string") {
      options[properties[0]] = args[0];
      options[properties[1]] = args[1];
    } else {
      options = args[0];
    }

    return options;
  }

  function processOptions(options) {
    // any post-processing of standard options here. for now this
    // is limited to simply choosing whether to show a header or
    // not based on whether the user supplied a title
    options.header = !!options.title;
    return options;
  }

  function mergeArguments(defaults, args, properties) {
    return processOptions(
      $.extend(true, {}, defaults, mapArguments(args, properties))
    );
  }

  function createLabels() {
    var buttons = {};

    for (var i = 0, j = arguments.length; i < j; i++) {
      var argument = arguments[i];
      var key = argument.toLowerCase();
      var value = argument.toUpperCase();

      buttons[key] = {
        label: _t(value)
      };
    }

    return buttons;
  }

  function createButtons() {
    return {
      buttons: createLabels.apply(null, arguments)
    };
  }

  exports.alert = function() {
    var options;

    options = mergeArguments(createButtons("ok"), arguments, ["message", "callback"]);

    if (options.callback && !$.isFunction(options.callback)) {
      throw new Error("alert requires callback property to be a function when provided");
    }

    /**
     * overrides
     */
    options.buttons.ok.callback = options.onEscape = function() {
      if ($.isFunction(options.callback)) {
        return options.callback();
      }
      return true;
    };

    return exports.dialog(options);
  };

  exports.confirm = function() {
    var options;

    options = mergeArguments(createButtons("cancel", "confirm"), arguments, ["message", "callback"]);

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(false);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(true);
    };

    // confirm specific validation
    if (!$.isFunction(options.callback)) {
      throw new Error("confirm requires a callback");
    }

    return exports.dialog(options);
  };

  exports.prompt = function() {
    var options;
    var defaults;
    var dialog;
    var form;
    var input;
    var shouldShow;

    // we have to create our form first otherwise
    // its value is undefined when gearing up our options
    // @TODO this could be solved by allowing message to
    // be a function instead...
    form = $(templates.form);

    defaults = {
      buttons: createLabels("cancel", "confirm"),
      value: ""
    };

    options = mergeArguments(defaults, arguments, ["title", "callback"]);

    // capture the user's show value; we always set this to false before
    // spawning the dialog to give us a chance to attach some handlers to
    // it, but we need to make sure we respect a preference not to show it
    shouldShow = (options.show === undefined) ? true : options.show;

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.message = form;

    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(null);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(input.val());
    };

    options.show = false;

    // prompt specific validation
    if (!options.title) {
      throw new Error("prompt requires a title");
    }

    if (!$.isFunction(options.callback)) {
      throw new Error("prompt requires a callback");
    }

    // create the input
    input = $(templates.inputs.text);
    input.val(options.value);

    // now place it in our form
    form.append(input);

    form.on("submit", function(e) {
      e.preventDefault();
      // @TODO can we actually click *the* button object instead?
      // e.g. buttons.confirm.click() or similar
      dialog.find(".btn-primary").click();
    });

    dialog = exports.dialog(options);

    // clear the existing handler focusing the submit button...
    dialog.off("shown.bs.modal");

    // ...and replace it with one focusing our input, if possible
    dialog.on("shown.bs.modal", function() {
      input.focus();
    });

    if (shouldShow === true) {
      dialog.modal("show");
    }

    return dialog;
  };

  exports.dialog = function(options) {
    options = sanitize(options);

    var dialog = $(templates.dialog);
    var body = dialog.find(".modal-body");
    var buttons = options.buttons;
    var buttonStr = "";
    var callbacks = {
      onEscape: options.onEscape
    };

    each(buttons, function(key, button) {

      // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
      // can we just build up button elements instead? slower but neater. Then button
      // can just become a template too
      buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
      callbacks[key] = button.callback;
    });

    body.find(".bootbox-body").html(options.message);

    if (options.animate === true) {
      dialog.addClass("fade");
    }

    if (options.className) {
      dialog.addClass(options.className);
    }

    if (options.header) {
      body.before(templates.header);
    }

    if (options.closeButton) {
      var closeButton = $(templates.closeButton);

      if (options.header) {
        dialog.find(".modal-header").prepend(closeButton);
      } else {
        closeButton.css("margin-top", "-10px").prependTo(body);
      }
    }

    if (options.header && options.title) {
      dialog.find(".modal-title").html(options.title);
    }

    if (buttonStr.length) {
      body.after(templates.footer);
      dialog.find(".modal-footer").html(buttonStr);
    }

    /**
     * Bootstrap event listeners; used handle extra
     * setup & teardown required after the underlying
     * modal has performed certain actions
     */

    dialog.on("hidden.bs.modal", function(e) {
      // ensure we don't accidentally intercept hidden events triggered
      // by children of the current dialog. We shouldn't anymore now BS
      // namespaces its events; but still worth doing
      if (e.target === this) {
        dialog.remove();
      }
    });

    /*
    dialog.on("show.bs.modal", function() {
      // sadly this doesn't work; show is called *just* before
      // the backdrop is added so we'd need a setTimeout hack or
      // otherwise... leaving in as would be nice
      if (options.backdrop) {
        dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
      }
    });
    */

    dialog.on("shown.bs.modal", function() {
      dialog.find(".btn-primary:first").focus();
    });

    /**
     * Bootbox event listeners; experimental and may not last
     * just an attempt to decouple some behaviours from their
     * respective triggers
     */

    dialog.on("escape.close.bb", function(e) {
      if (callbacks.onEscape) {
        processCallback(e, dialog, callbacks.onEscape);
      }
    });

    /**
     * Standard jQuery event listeners; used to handle user
     * interaction with our dialog
     */

    dialog.on("click", ".modal-footer button", function(e) {
      var callbackKey = $(this).data("bb-handler");

      processCallback(e, dialog, callbacks[callbackKey]);

    });

    dialog.on("click", ".bootbox-close-button", function(e) {
      // onEscape might be falsy but that's fine; the fact is
      // if the user has managed to click the close button we
      // have to close the dialog, callback or not
      processCallback(e, dialog, callbacks.onEscape);
    });

    dialog.on("keyup", function(e) {
      if (e.which === 27) {
        dialog.trigger("escape.close.bb");
      }
    });

    // the remainder of this method simply deals with adding our
    // dialogent to the DOM, augmenting it with Bootstrap's modal
    // functionality and then giving the resulting object back
    // to our caller

    appendTo.append(dialog);

    dialog.modal({
      backdrop: options.backdrop,
      keyboard: false,
      show: false
    });

    if (options.show) {
      dialog.modal("show");
    }

    // @TODO should we return the raw element here or should
    // we wrap it in an object on which we can expose some neater
    // methods, e.g. var d = bootbox.alert(); d.hide(); instead
    // of d.modal("hide");

   /*
    function BBDialog(elem) {
      this.elem = elem;
    }

    BBDialog.prototype = {
      hide: function() {
        return this.elem.modal("hide");
      },
      show: function() {
        return this.elem.modal("show");
      }
    };
    */

    return dialog;

  };

  exports.setDefaults = function(values) {
    $.extend(defaults, values);
  };

  exports.hideAll = function() {
    $(".bootbox").modal("hide");
  };

  /**
   * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
   * unlikely to be required. If this gets too large it can be split out into separate JS files.
   */
  var locales = {
    br : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Sim"
    },
    da : {
      OK      : "OK",
      CANCEL  : "Annuller",
      CONFIRM : "Accepter"
    },
    de : {
      OK      : "OK",
      CANCEL  : "Abbrechen",
      CONFIRM : "Akzeptieren"
    },
    en : {
      OK      : "OK",
      CANCEL  : "Cancel",
      CONFIRM : "OK"
    },
    es : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Aceptar"
    },
    fi : {
      OK      : "OK",
      CANCEL  : "Peruuta",
      CONFIRM : "OK"
    },
    fr : {
      OK      : "OK",
      CANCEL  : "Annuler",
      CONFIRM : "D'accord"
    },
    it : {
      OK      : "OK",
      CANCEL  : "Annulla",
      CONFIRM : "Conferma"
    },
    nl : {
      OK      : "OK",
      CANCEL  : "Annuleren",
      CONFIRM : "Accepteren"
    },
    pl : {
      OK      : "OK",
      CANCEL  : "Anuluj",
      CONFIRM : "Potwierdź"
    },
    ru : {
      OK      : "OK",
      CANCEL  : "Отмена",
      CONFIRM : "Применить"
    },
    zh_CN : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "确认"
    },
    zh_TW : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "確認"
    }
  };

  return exports;

}(document, window.jQuery));
<commit_msg>ensure prompt input is full width<commit_after>/**
 * bootbox.js [v4.x/master branch]
 *
 * http://bootboxjs.com/license.txt
 */
// @see https://github.com/makeusabrew/bootbox/issues/71
window.bootbox = window.bootbox || (function(document, $, undefined) {
  "use strict";

  // the base DOM structure needed to create a modal
  var templates = {
    dialog:
      "
" +
        "
" +
          "
" +
            "

" +
          "
" +
        "
" +
      "
",
    header:
      "
" +
        "

" +
      "
",
    footer:
      "

",
    closeButton:
      "<button type='button' class='bootbox-close-button close'>×</button>",
    form:
      "<form class='bootbox-form'></form>",
    inputs: {
      text:
        "<input class='bootbox-input form-control' autocomplete=off type=text />"
    }
  };

  // cache a reference to the jQueryfied body element
  var appendTo = $("body");

  var defaults = {
    // default language
    locale: "en",
    // show backdrop or not
    backdrop: true,
    // animate the modal in/out
    animate: true,
    // additional class string applied to the top level dialog
    className: null,
    // show the modal header or not
    header: false,
    // whether or not to include a close button, if a header is present
    closeButton: true,
    // show the dialog immediately by default
    show: true
  };

  // our public object; augmented after our private API
  var exports = {};

  /**
   * @private
   */
  function _t(key) {
    return locales[defaults.locale][key] || locales.en[key];
  }

  function processCallback(e, dialog, callback) {
    e.preventDefault();

    // by default we assume a callback will get rid of the dialog,
    // although it is given the opportunity to override this

    // so, if the callback can be invoked and it *explicitly returns false*
    // then we'll set a flag to keep the dialog active...
    var preserveDialog = $.isFunction(callback) && callback(e) === false;

    // ... otherwise we'll bin it
    if (!preserveDialog) {
      dialog.modal("hide");
    }
  }

  function getKeyLength(obj) {
    // @TODO defer to Object.keys(x).length if available?
    var k, t = 0;
    for (k in obj) {
      t ++;
    }
    return t;
  }

  function each(collection, iterator) {
    var index = 0;
    $.each(collection, function(key, value) {
      iterator(key, value, index++);
    });
  }

  function sanitize(options) {
    var buttons;
    var total;

    if (typeof options !== "object") {
      throw new Error("Please supply an object of options");
    }

    if (!options.message) {
      throw new Error("Please specify a message");
    }

    // make sure any supplied options take precedence over defaults
    options = $.extend({}, defaults, options);

    if (!options.buttons) {
      options.buttons = {};
    }

    // we only support Bootstrap's "static" and false backdrop args
    // supporting true would mean you could dismiss the dialog without
    // explicitly interacting with it
    options.backdrop = options.backdrop ? "static" : false;

    buttons = options.buttons;

    total = getKeyLength(buttons);

    each(buttons, function(key, button, index) {

      if (!button.label) {
        throw new Error("button with key " + key + " requires a label");
      }

      if (!button.className) {
        if (total <= 2 && index === total-1) {
          // always add a primary to the main option in a two-button dialog
          button.className = "btn-primary";
        } else {
          button.className = "btn-default";
        }
      }
    });

    return options;
  }

  function mapArguments(args, properties) {
    var argn = args.length;
    var options = {};

    if (argn < 1 || argn > 2) {
      throw new Error("Invalid argument length");
    }

    if (argn === 2 || typeof args[0] === "string") {
      options[properties[0]] = args[0];
      options[properties[1]] = args[1];
    } else {
      options = args[0];
    }

    return options;
  }

  function processOptions(options) {
    // any post-processing of standard options here. for now this
    // is limited to simply choosing whether to show a header or
    // not based on whether the user supplied a title
    options.header = !!options.title;
    return options;
  }

  function mergeArguments(defaults, args, properties) {
    return processOptions(
      $.extend(true, {}, defaults, mapArguments(args, properties))
    );
  }

  function createLabels() {
    var buttons = {};

    for (var i = 0, j = arguments.length; i < j; i++) {
      var argument = arguments[i];
      var key = argument.toLowerCase();
      var value = argument.toUpperCase();

      buttons[key] = {
        label: _t(value)
      };
    }

    return buttons;
  }

  function createButtons() {
    return {
      buttons: createLabels.apply(null, arguments)
    };
  }

  exports.alert = function() {
    var options;

    options = mergeArguments(createButtons("ok"), arguments, ["message", "callback"]);

    if (options.callback && !$.isFunction(options.callback)) {
      throw new Error("alert requires callback property to be a function when provided");
    }

    /**
     * overrides
     */
    options.buttons.ok.callback = options.onEscape = function() {
      if ($.isFunction(options.callback)) {
        return options.callback();
      }
      return true;
    };

    return exports.dialog(options);
  };

  exports.confirm = function() {
    var options;

    options = mergeArguments(createButtons("cancel", "confirm"), arguments, ["message", "callback"]);

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(false);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(true);
    };

    // confirm specific validation
    if (!$.isFunction(options.callback)) {
      throw new Error("confirm requires a callback");
    }

    return exports.dialog(options);
  };

  exports.prompt = function() {
    var options;
    var defaults;
    var dialog;
    var form;
    var input;
    var shouldShow;

    // we have to create our form first otherwise
    // its value is undefined when gearing up our options
    // @TODO this could be solved by allowing message to
    // be a function instead...
    form = $(templates.form);

    defaults = {
      buttons: createLabels("cancel", "confirm"),
      value: ""
    };

    options = mergeArguments(defaults, arguments, ["title", "callback"]);

    // capture the user's show value; we always set this to false before
    // spawning the dialog to give us a chance to attach some handlers to
    // it, but we need to make sure we respect a preference not to show it
    shouldShow = (options.show === undefined) ? true : options.show;

    /**
     * overrides; undo anything the user tried to set they shouldn't have
     */
    options.message = form;

    options.buttons.cancel.callback = options.onEscape = function() {
      return options.callback(null);
    };

    options.buttons.confirm.callback = function() {
      return options.callback(input.val());
    };

    options.show = false;

    // prompt specific validation
    if (!options.title) {
      throw new Error("prompt requires a title");
    }

    if (!$.isFunction(options.callback)) {
      throw new Error("prompt requires a callback");
    }

    // create the input
    input = $(templates.inputs.text);
    input.val(options.value);

    // now place it in our form
    form.append(input);

    form.on("submit", function(e) {
      e.preventDefault();
      // @TODO can we actually click *the* button object instead?
      // e.g. buttons.confirm.click() or similar
      dialog.find(".btn-primary").click();
    });

    dialog = exports.dialog(options);

    // clear the existing handler focusing the submit button...
    dialog.off("shown.bs.modal");

    // ...and replace it with one focusing our input, if possible
    dialog.on("shown.bs.modal", function() {
      input.focus();
    });

    if (shouldShow === true) {
      dialog.modal("show");
    }

    return dialog;
  };

  exports.dialog = function(options) {
    options = sanitize(options);

    var dialog = $(templates.dialog);
    var body = dialog.find(".modal-body");
    var buttons = options.buttons;
    var buttonStr = "";
    var callbacks = {
      onEscape: options.onEscape
    };

    each(buttons, function(key, button) {

      // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
      // can we just build up button elements instead? slower but neater. Then button
      // can just become a template too
      buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
      callbacks[key] = button.callback;
    });

    body.find(".bootbox-body").html(options.message);

    if (options.animate === true) {
      dialog.addClass("fade");
    }

    if (options.className) {
      dialog.addClass(options.className);
    }

    if (options.header) {
      body.before(templates.header);
    }

    if (options.closeButton) {
      var closeButton = $(templates.closeButton);

      if (options.header) {
        dialog.find(".modal-header").prepend(closeButton);
      } else {
        closeButton.css("margin-top", "-10px").prependTo(body);
      }
    }

    if (options.header && options.title) {
      dialog.find(".modal-title").html(options.title);
    }

    if (buttonStr.length) {
      body.after(templates.footer);
      dialog.find(".modal-footer").html(buttonStr);
    }

    /**
     * Bootstrap event listeners; used handle extra
     * setup & teardown required after the underlying
     * modal has performed certain actions
     */

    dialog.on("hidden.bs.modal", function(e) {
      // ensure we don't accidentally intercept hidden events triggered
      // by children of the current dialog. We shouldn't anymore now BS
      // namespaces its events; but still worth doing
      if (e.target === this) {
        dialog.remove();
      }
    });

    /*
    dialog.on("show.bs.modal", function() {
      // sadly this doesn't work; show is called *just* before
      // the backdrop is added so we'd need a setTimeout hack or
      // otherwise... leaving in as would be nice
      if (options.backdrop) {
        dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
      }
    });
    */

    dialog.on("shown.bs.modal", function() {
      dialog.find(".btn-primary:first").focus();
    });

    /**
     * Bootbox event listeners; experimental and may not last
     * just an attempt to decouple some behaviours from their
     * respective triggers
     */

    dialog.on("escape.close.bb", function(e) {
      if (callbacks.onEscape) {
        processCallback(e, dialog, callbacks.onEscape);
      }
    });

    /**
     * Standard jQuery event listeners; used to handle user
     * interaction with our dialog
     */

    dialog.on("click", ".modal-footer button", function(e) {
      var callbackKey = $(this).data("bb-handler");

      processCallback(e, dialog, callbacks[callbackKey]);

    });

    dialog.on("click", ".bootbox-close-button", function(e) {
      // onEscape might be falsy but that's fine; the fact is
      // if the user has managed to click the close button we
      // have to close the dialog, callback or not
      processCallback(e, dialog, callbacks.onEscape);
    });

    dialog.on("keyup", function(e) {
      if (e.which === 27) {
        dialog.trigger("escape.close.bb");
      }
    });

    // the remainder of this method simply deals with adding our
    // dialogent to the DOM, augmenting it with Bootstrap's modal
    // functionality and then giving the resulting object back
    // to our caller

    appendTo.append(dialog);

    dialog.modal({
      backdrop: options.backdrop,
      keyboard: false,
      show: false
    });

    if (options.show) {
      dialog.modal("show");
    }

    // @TODO should we return the raw element here or should
    // we wrap it in an object on which we can expose some neater
    // methods, e.g. var d = bootbox.alert(); d.hide(); instead
    // of d.modal("hide");

   /*
    function BBDialog(elem) {
      this.elem = elem;
    }

    BBDialog.prototype = {
      hide: function() {
        return this.elem.modal("hide");
      },
      show: function() {
        return this.elem.modal("show");
      }
    };
    */

    return dialog;

  };

  exports.setDefaults = function(values) {
    $.extend(defaults, values);
  };

  exports.hideAll = function() {
    $(".bootbox").modal("hide");
  };

  /**
   * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
   * unlikely to be required. If this gets too large it can be split out into separate JS files.
   */
  var locales = {
    br : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Sim"
    },
    da : {
      OK      : "OK",
      CANCEL  : "Annuller",
      CONFIRM : "Accepter"
    },
    de : {
      OK      : "OK",
      CANCEL  : "Abbrechen",
      CONFIRM : "Akzeptieren"
    },
    en : {
      OK      : "OK",
      CANCEL  : "Cancel",
      CONFIRM : "OK"
    },
    es : {
      OK      : "OK",
      CANCEL  : "Cancelar",
      CONFIRM : "Aceptar"
    },
    fi : {
      OK      : "OK",
      CANCEL  : "Peruuta",
      CONFIRM : "OK"
    },
    fr : {
      OK      : "OK",
      CANCEL  : "Annuler",
      CONFIRM : "D'accord"
    },
    it : {
      OK      : "OK",
      CANCEL  : "Annulla",
      CONFIRM : "Conferma"
    },
    nl : {
      OK      : "OK",
      CANCEL  : "Annuleren",
      CONFIRM : "Accepteren"
    },
    pl : {
      OK      : "OK",
      CANCEL  : "Anuluj",
      CONFIRM : "Potwierdź"
    },
    ru : {
      OK      : "OK",
      CANCEL  : "Отмена",
      CONFIRM : "Применить"
    },
    zh_CN : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "确认"
    },
    zh_TW : {
      OK      : "OK",
      CANCEL  : "取消",
      CONFIRM : "確認"
    }
  };

  return exports;

}(document, window.jQuery));
<|endoftext|>


<commit_before>/*
 © Copyright IBM Corp. 2017, 2018
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

'use strict';

const Handlebars = require('../lib/handlebars.js');
const Generator = require('yeoman-generator');
const Utils = require('../lib/utils');

module.exports = class extends Generator {
	constructor(args, opts) {
		super(args, opts);
		if (opts.cloudContext) {
			this.opts = opts.cloudContext
			this.opts.libertyVersion = opts.libertyVersion
		} else {
			this.opts = opts
		}
		if (typeof (opts.bluemix) === 'string') {
			this.bluemix = JSON.parse(opts.bluemix || '{}');
		} else if (typeof (opts.bluemix) === 'object') {
			this.bluemix = opts.bluemix;
		}

	}

	configuring() {
		this.manifestConfig = {};
		this.manifestConfig.env = {};
		this.toolchainConfig = {};
		this.pipelineConfig = {
			buildJobProps: {
				artifact_dir: "''"
			},
			triggersType: 'commit'
		};
		this.deployment = {
			type: 'CF',
			name: this.bluemix.name,
			language: this.bluemix.backendPlatform
		};

		this.name = undefined;
		if (this.bluemix.server) {
			this.name = this.bluemix.server.name;
			this.manifestConfig = Object.assign(this.manifestConfig, this.bluemix.server);
			this.deployment = Object.assign(this.deployment, this.bluemix.server.cloudDeploymentOptions);
			this.deployment.type = this.bluemix.server.cloudDeploymentType || 'CF';
			this.deployment.chartName = Utils.sanitizeAlphaNumLowerCase(this.name || this.bluemix.name);
			this.deployment.scriptsDir = '.bluemix/scripts';
			if (!this.deployment.kubeClusterNamespace) {
				this.deployment.kubeClusterNamespace = 'default';
			}
			if (!this.deployment.imageRegistryNamespace) {
				this.deployment.imageRegistryNamespace = 'my_registry_ns';
			}
		} else {
			this.name = this.bluemix.name;
			this.manifestConfig.name = this.bluemix.name;
			this.deployment.type = this.bluemix.cloudDeploymentType || 'CF';
		}

		this.toolchainConfig.repoType = this.opts.repoType || "clone";
		switch (this.bluemix.backendPlatform) {
			case 'NODE':
				this._configureNode();
				break;
			case 'SWIFT':
				this._configureSwift();
				break;
			case 'JAVA':
				this._configureLiberty();
				this._configureJavaCommon();
				break;
			case 'SPRING':
				this._configureJavaCommon();
				this._configureSpring();
				break;
			case 'PYTHON':
				this._configurePython();
				break;
			case 'DJANGO':
				this._configureDjango();
				break;
			case 'GO':
				this._configureGo();
				break;
			default:
				throw new Error(`Language ${this.bluemix.backendPlatform} was not one of the valid languages: NODE, SWIFT, JAVA, SPRING, DJANGO, PYTHON, or GO`);
		}
		if (this.manifestConfig && this.manifestConfig.ignorePaths) {
			this.cfIgnoreContent = this.cfIgnoreContent.concat(this.manifestConfig.ignorePaths);
		}

		this.pipelineConfig.postBuildScript = this.fs.read(this.templatePath('post_build.txt'));

		if (this.pipelineConfig.buildJobProps && this.pipelineConfig.buildJobProps.script) {
			this.pipelineConfig.buildJobProps.script += '\n\n' + this.pipelineConfig.postBuildScript;
		} else if (!this.pipelineConfig.buildJobProps.script) {
			Object.assign(this.pipelineConfig.buildJobProps, {
				build_type: 'shell',
				script: '|-\n' +
				'      #!/bin/bash\n' +
				this.pipelineConfig.postBuildScript
			});
		}
	}

	/***
	 * Get the highest memory size available
	 *
	 * @params manifestMemoryConfig {string} the memory allocaated h
	 */
	_getHighestMemorySize(manifestMemoryConfig, userDefinedMinMemory){
		if(!userDefinedMinMemory) {
			return manifestMemoryConfig;
		} else if (!manifestMemoryConfig && userDefinedMinMemory) {
			return userDefinedMinMemory;
		}

		const memMap = {
			k: 1,
			m: 2,
			g: 3
		};
		const manifestSize = manifestMemoryConfig.replace(/[^MmGgKk]/g, '');
		const userDefinedMinSize = userDefinedMinMemory.replace(/[^MmGgKk]/g, '');
		let highestAvailableSize;

		if(memMap[manifestSize.toLowerCase()] > memMap[userDefinedMinSize.toLowerCase()]){
			highestAvailableSize = manifestMemoryConfig;
		} else if (memMap[manifestSize.toLowerCase()] < memMap[userDefinedMinSize.toLowerCase()]){
			highestAvailableSize = userDefinedMinMemory;
		} else {
			const manifestValue = parseInt(manifestSize.replace(/[M,m,G,g,K,k]/g, ''));
			const definedValue = parseInt(userDefinedMinSize.replace(/[M,m,G,g,K,k]/g, ''));

			highestAvailableSize = manifestValue > definedValue ? manifestMemoryConfig : userDefinedMinMemory;
		}

		return highestAvailableSize;
	}

	_configureNode() {
		this.manifestConfig.buildpack = 'sdk-for-nodejs';
		this.manifestConfig.command = 'npm start';
		this.manifestConfig.memory = this._getHighestMemorySize(this.manifestConfig.memory, this.opts.nodeCFMinMemory);
		this.cfIgnoreContent = ['.git/', 'node_modules/', 'test/', 'vcap-local.js'];
	}

	_configureGo() {
		// Need a direct github link becasue 'go_buildpack' doesn't have dep support
		this.manifestConfig.buildpack = 'https://github.com/cloudfoundry/go-buildpack.git#v1.8.25';
		this.manifestConfig.command = undefined;
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.manifestConfig.env.GOPACKAGENAME = this.bluemix.sanitizedName;
		this.cfIgnoreContent = ['.git/', 'vendor/'];
	}

	_configureSwift() {
		this.manifestConfig.buildpack = 'swift_buildpack';
		this.manifestConfig.command = this.bluemix.name ? ("\"'" + `${this.bluemix.name}` + "'\"") : undefined;
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.pipelineConfig.swift = true;
		this.cfIgnoreContent = ['.build/*', '.build-ubuntu/*', 'Packages/*'];
	}

	_configureJavaCommon() {
		if (this.opts.appName) {
			this.manifestConfig.name = this.opts.appName;
			this.name = this.opts.appName;
		}
		if (this.opts.createType === 'bff/liberty') {
			this.manifestConfig.env.OPENAPI_SPEC = `/${this.name}/swagger/api`;
		}
		if (this.opts.createType === 'bff/spring') {
			this.manifestConfig.env.OPENAPI_SPEC = '/swagger/api';
		}

		if (this.opts.createType && this.opts.createType.startsWith('enable/')) {
			this.toolchainConfig.repoType = 'link';
		}
		let buildCommand = this.opts.buildType === 'maven' ? '      mvn -N io.takari:maven:wrapper -Dmaven=3.5.0\n      ./mvnw install -DskipTests' : '      gradle build';
		this.pipelineConfig.javaBuildScriptContent = 'export JAVA_HOME=$JAVA8_HOME\n' + buildCommand;
		this.pipelineConfig.buildJobProps = {
			build_type: 'shell',
			script: '|\n' +
			'      #!/bin/bash\n' +
			'      ' + this.pipelineConfig.javaBuildScriptContent
		};
	}

	_configureLiberty() {
		this.cfIgnoreContent = ['/.classpath', '/.project', '/.settings', '/src/main/liberty/config/server.env', 'target/', 'build/'];
		this.manifestConfig.buildpack = 'liberty-for-java';
		this.manifestConfig.memory = this.manifestConfig.memory || '512M';
		let buildDir = (this.opts.buildType && this.opts.buildType === 'gradle') ? 'build' : 'target';
		let zipPath = `${buildDir}/${this.opts.artifactId}-${this.opts.version}.zip`
		this.manifestConfig.path = `./${zipPath}`;
		let excludes = [];
		if (this.opts.libertyVersion === 'beta') {
			this.manifestConfig.env.IBM_LIBERTY_BETA = 'true'
			this.manifestConfig.env.JBP_CONFIG_LIBERTY = '\"version: +\"'
		}
		if (this.bluemix.cloudant) {
			excludes.push('cloudantNoSQLDB=config');
		}
		if (this.bluemix.objectStorage) {
			excludes.push('Object-Storage=config');
		}
		if (excludes.length === 1) {
			this.manifestConfig.env.services_autoconfig_excludes = excludes[0];
		}
		if (excludes.length === 2) {
			this.manifestConfig.env.services_autoconfig_excludes = excludes[0] + ' ' + excludes[1];
		}
		this.pipelineConfig.pushCommand = 'cf push "${CF_APP}" -p ' + zipPath;
	}

	_configureSpring() {
		this.cfIgnoreContent = ['/.classpath', '/.project', '/.settings', '/src/main/resources/application-local.properties', 'target/', 'build/'];
		this.manifestConfig.buildpack = 'java_buildpack';
		this.manifestConfig.memory = this.manifestConfig.memory || '256M';
		let buildDir = (this.opts.buildType && this.opts.buildType === 'gradle') ? 'build/libs' : 'target';
		let jarPath = `${buildDir}/${this.opts.artifactId}-${this.opts.version}.jar`;
		this.manifestConfig.path = `./${jarPath}`;
		this.pipelineConfig.pushCommand = 'cf push "${CF_APP}" -p ' + jarPath;
	}

	_configurePython() {
		// buildpack is left blank; bluemix will auto detect
		this.manifestConfig.buildpack = 'python_buildpack';
		this.manifestConfig.command = this.opts.enable ?
			'echo No run command specified in manifest.yml' :
			'python manage.py start 0.0.0.0:$PORT';
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.manifestConfig.env.FLASK_APP = 'server';
		this.manifestConfig.env.FLASK_DEBUG = 'false';
		this.cfIgnoreContent = ['.pyc', '.egg-info'];
	}

	_configureDjango() {
		// buildpack is left blank; bluemix will auto detect
		this.manifestConfig.buildpack = 'python_buildpack';
		this.manifestConfig.command = this.opts.enable ?
			'echo No run command specified in manifest.yml' :
			`gunicorn --env DJANGO_SETTINGS_MODULE=${this.bluemix.name}.settings.production ${this.bluemix.name}.wsgi -b 0.0.0.0:$PORT`;
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.cfIgnoreContent = ['.pyc', '.egg-info'];
	}

	cleanUpPass() {
		if (this.manifestConfig && this.manifestConfig.env && Object.keys(this.manifestConfig.env).length < 1) {
			delete this.manifestConfig.env;
		}
		if (this.cfIgnoreContent) {
			this.cfIgnoreContent = this.cfIgnoreContent.join('\n');
		}
	}

	writing() {
		//skip writing files if platforms is specified via options and it doesn't include bluemix
		if (this.opts.platforms && !this.opts.platforms.includes('bluemix')) {
			return;
		}
		// write manifest.yml file
		this._writeHandlebarsFile('manifest_master.yml', 'manifest.yml', this.manifestConfig)

		// if cfIgnnoreContent exists, create/write .cfignore file
		if (this.cfIgnoreContent) {
			this.fs.write('.cfignore', this.cfIgnoreContent);
		}

		// create .bluemix directory for toolchain/devops related files
		this._writeHandlebarsFile('toolchain_master.yml', '.bluemix/toolchain.yml', {
			name: this.opts.toolchainName || this.name,
			org: this.opts.deploymentOrg || "{{organization}}",
			space: this.opts.deploymentSpace || "{{space}}",
			region: this.opts.deploymentRegion || "{{region}}",
			repoType: this.toolchainConfig.repoType,
			deployment: this.deployment
		});

		this._writeHandlebarsFile('deploy_master.json', '.bluemix/deploy.json', {
			deployment: this.deployment
		});

		this._writeHandlebarsFile('container_build.sh', '.bluemix/scripts/container_build.sh', {
			deployment: this.deployment
		});

		this._writeHandlebarsFile('kube_deploy.sh', '.bluemix/scripts/kube_deploy.sh', {
			deployment: this.deployment
		});

		if (Utils.sanitizeAlphaNumLowerCase(this.deployment.type) === "vsi") {
			this._writeHandlebarsFile('vsi_pipeline_master.yml', '.bluemix/pipeline.yml', {
				config: this.pipelineConfig,
				name: this.name,
				lowercaseName: Utils.sanitizeAlphaNumLowerCase(this.name),
				deployment: this.deployment
			});
		}
		else {
			this._writeHandlebarsFile('pipeline_master.yml', '.bluemix/pipeline.yml', {
				config: this.pipelineConfig,
				deployment: this.deployment
			});
		}

	}

	_writeHandlebarsFile(templateFile, destinationFile, data) {
		let template = this.fs.read(this.templatePath(templateFile));
		let compiledTemplate = Handlebars.compile(template);
		let output = compiledTemplate(data);
		this.fs.write(this.destinationPath(destinationFile), output);
	}
};
<commit_msg>fix: More memory for CF Spring<commit_after>/*
 © Copyright IBM Corp. 2017, 2018
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

'use strict';

const Handlebars = require('../lib/handlebars.js');
const Generator = require('yeoman-generator');
const Utils = require('../lib/utils');

module.exports = class extends Generator {
	constructor(args, opts) {
		super(args, opts);
		if (opts.cloudContext) {
			this.opts = opts.cloudContext
			this.opts.libertyVersion = opts.libertyVersion
		} else {
			this.opts = opts
		}
		if (typeof (opts.bluemix) === 'string') {
			this.bluemix = JSON.parse(opts.bluemix || '{}');
		} else if (typeof (opts.bluemix) === 'object') {
			this.bluemix = opts.bluemix;
		}

	}

	configuring() {
		this.manifestConfig = {};
		this.manifestConfig.env = {};
		this.toolchainConfig = {};
		this.pipelineConfig = {
			buildJobProps: {
				artifact_dir: "''"
			},
			triggersType: 'commit'
		};
		this.deployment = {
			type: 'CF',
			name: this.bluemix.name,
			language: this.bluemix.backendPlatform
		};

		this.name = undefined;
		if (this.bluemix.server) {
			this.name = this.bluemix.server.name;
			this.manifestConfig = Object.assign(this.manifestConfig, this.bluemix.server);
			this.deployment = Object.assign(this.deployment, this.bluemix.server.cloudDeploymentOptions);
			this.deployment.type = this.bluemix.server.cloudDeploymentType || 'CF';
			this.deployment.chartName = Utils.sanitizeAlphaNumLowerCase(this.name || this.bluemix.name);
			this.deployment.scriptsDir = '.bluemix/scripts';
			if (!this.deployment.kubeClusterNamespace) {
				this.deployment.kubeClusterNamespace = 'default';
			}
			if (!this.deployment.imageRegistryNamespace) {
				this.deployment.imageRegistryNamespace = 'my_registry_ns';
			}
		} else {
			this.name = this.bluemix.name;
			this.manifestConfig.name = this.bluemix.name;
			this.deployment.type = this.bluemix.cloudDeploymentType || 'CF';
		}

		this.toolchainConfig.repoType = this.opts.repoType || "clone";
		switch (this.bluemix.backendPlatform) {
			case 'NODE':
				this._configureNode();
				break;
			case 'SWIFT':
				this._configureSwift();
				break;
			case 'JAVA':
				this._configureLiberty();
				this._configureJavaCommon();
				break;
			case 'SPRING':
				this._configureJavaCommon();
				this._configureSpring();
				break;
			case 'PYTHON':
				this._configurePython();
				break;
			case 'DJANGO':
				this._configureDjango();
				break;
			case 'GO':
				this._configureGo();
				break;
			default:
				throw new Error(`Language ${this.bluemix.backendPlatform} was not one of the valid languages: NODE, SWIFT, JAVA, SPRING, DJANGO, PYTHON, or GO`);
		}
		if (this.manifestConfig && this.manifestConfig.ignorePaths) {
			this.cfIgnoreContent = this.cfIgnoreContent.concat(this.manifestConfig.ignorePaths);
		}

		this.pipelineConfig.postBuildScript = this.fs.read(this.templatePath('post_build.txt'));

		if (this.pipelineConfig.buildJobProps && this.pipelineConfig.buildJobProps.script) {
			this.pipelineConfig.buildJobProps.script += '\n\n' + this.pipelineConfig.postBuildScript;
		} else if (!this.pipelineConfig.buildJobProps.script) {
			Object.assign(this.pipelineConfig.buildJobProps, {
				build_type: 'shell',
				script: '|-\n' +
				'      #!/bin/bash\n' +
				this.pipelineConfig.postBuildScript
			});
		}
	}

	/***
	 * Get the highest memory size available
	 *
	 * @params manifestMemoryConfig {string} the memory allocaated h
	 */
	_getHighestMemorySize(manifestMemoryConfig, userDefinedMinMemory){
		if(!userDefinedMinMemory) {
			return manifestMemoryConfig;
		} else if (!manifestMemoryConfig && userDefinedMinMemory) {
			return userDefinedMinMemory;
		}

		const memMap = {
			k: 1,
			m: 2,
			g: 3
		};
		const manifestSize = manifestMemoryConfig.replace(/[^MmGgKk]/g, '');
		const userDefinedMinSize = userDefinedMinMemory.replace(/[^MmGgKk]/g, '');
		let highestAvailableSize;

		if(memMap[manifestSize.toLowerCase()] > memMap[userDefinedMinSize.toLowerCase()]){
			highestAvailableSize = manifestMemoryConfig;
		} else if (memMap[manifestSize.toLowerCase()] < memMap[userDefinedMinSize.toLowerCase()]){
			highestAvailableSize = userDefinedMinMemory;
		} else {
			const manifestValue = parseInt(manifestSize.replace(/[M,m,G,g,K,k]/g, ''));
			const definedValue = parseInt(userDefinedMinSize.replace(/[M,m,G,g,K,k]/g, ''));

			highestAvailableSize = manifestValue > definedValue ? manifestMemoryConfig : userDefinedMinMemory;
		}

		return highestAvailableSize;
	}

	_configureNode() {
		this.manifestConfig.buildpack = 'sdk-for-nodejs';
		this.manifestConfig.command = 'npm start';
		this.manifestConfig.memory = this._getHighestMemorySize(this.manifestConfig.memory, this.opts.nodeCFMinMemory);
		this.cfIgnoreContent = ['.git/', 'node_modules/', 'test/', 'vcap-local.js'];
	}

	_configureGo() {
		// Need a direct github link becasue 'go_buildpack' doesn't have dep support
		this.manifestConfig.buildpack = 'https://github.com/cloudfoundry/go-buildpack.git#v1.8.25';
		this.manifestConfig.command = undefined;
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.manifestConfig.env.GOPACKAGENAME = this.bluemix.sanitizedName;
		this.cfIgnoreContent = ['.git/', 'vendor/'];
	}

	_configureSwift() {
		this.manifestConfig.buildpack = 'swift_buildpack';
		this.manifestConfig.command = this.bluemix.name ? ("\"'" + `${this.bluemix.name}` + "'\"") : undefined;
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.pipelineConfig.swift = true;
		this.cfIgnoreContent = ['.build/*', '.build-ubuntu/*', 'Packages/*'];
	}

	_configureJavaCommon() {
		if (this.opts.appName) {
			this.manifestConfig.name = this.opts.appName;
			this.name = this.opts.appName;
		}
		if (this.opts.createType === 'bff/liberty') {
			this.manifestConfig.env.OPENAPI_SPEC = `/${this.name}/swagger/api`;
		}
		if (this.opts.createType === 'bff/spring') {
			this.manifestConfig.env.OPENAPI_SPEC = '/swagger/api';
		}

		if (this.opts.createType && this.opts.createType.startsWith('enable/')) {
			this.toolchainConfig.repoType = 'link';
		}
		let buildCommand = this.opts.buildType === 'maven' ? '      mvn -N io.takari:maven:wrapper -Dmaven=3.5.0\n      ./mvnw install -DskipTests' : '      gradle build';
		this.pipelineConfig.javaBuildScriptContent = 'export JAVA_HOME=$JAVA8_HOME\n' + buildCommand;
		this.pipelineConfig.buildJobProps = {
			build_type: 'shell',
			script: '|\n' +
			'      #!/bin/bash\n' +
			'      ' + this.pipelineConfig.javaBuildScriptContent
		};
	}

	_configureLiberty() {
		this.cfIgnoreContent = ['/.classpath', '/.project', '/.settings', '/src/main/liberty/config/server.env', 'target/', 'build/'];
		this.manifestConfig.buildpack = 'liberty-for-java';
		this.manifestConfig.memory = this.manifestConfig.memory || '512M';
		let buildDir = (this.opts.buildType && this.opts.buildType === 'gradle') ? 'build' : 'target';
		let zipPath = `${buildDir}/${this.opts.artifactId}-${this.opts.version}.zip`
		this.manifestConfig.path = `./${zipPath}`;
		let excludes = [];
		if (this.opts.libertyVersion === 'beta') {
			this.manifestConfig.env.IBM_LIBERTY_BETA = 'true'
			this.manifestConfig.env.JBP_CONFIG_LIBERTY = '\"version: +\"'
		}
		if (this.bluemix.cloudant) {
			excludes.push('cloudantNoSQLDB=config');
		}
		if (this.bluemix.objectStorage) {
			excludes.push('Object-Storage=config');
		}
		if (excludes.length === 1) {
			this.manifestConfig.env.services_autoconfig_excludes = excludes[0];
		}
		if (excludes.length === 2) {
			this.manifestConfig.env.services_autoconfig_excludes = excludes[0] + ' ' + excludes[1];
		}
		this.pipelineConfig.pushCommand = 'cf push "${CF_APP}" -p ' + zipPath;
	}

	_configureSpring() {
		this.cfIgnoreContent = ['/.classpath', '/.project', '/.settings', '/src/main/resources/application-local.properties', 'target/', 'build/'];
		this.manifestConfig.buildpack = 'java_buildpack';
		this.manifestConfig.memory = this.manifestConfig.memory || '1024M';
		let buildDir = (this.opts.buildType && this.opts.buildType === 'gradle') ? 'build/libs' : 'target';
		let jarPath = `${buildDir}/${this.opts.artifactId}-${this.opts.version}.jar`;
		this.manifestConfig.path = `./${jarPath}`;
		this.pipelineConfig.pushCommand = 'cf push "${CF_APP}" -p ' + jarPath;
	}

	_configurePython() {
		// buildpack is left blank; bluemix will auto detect
		this.manifestConfig.buildpack = 'python_buildpack';
		this.manifestConfig.command = this.opts.enable ?
			'echo No run command specified in manifest.yml' :
			'python manage.py start 0.0.0.0:$PORT';
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.manifestConfig.env.FLASK_APP = 'server';
		this.manifestConfig.env.FLASK_DEBUG = 'false';
		this.cfIgnoreContent = ['.pyc', '.egg-info'];
	}

	_configureDjango() {
		// buildpack is left blank; bluemix will auto detect
		this.manifestConfig.buildpack = 'python_buildpack';
		this.manifestConfig.command = this.opts.enable ?
			'echo No run command specified in manifest.yml' :
			`gunicorn --env DJANGO_SETTINGS_MODULE=${this.bluemix.name}.settings.production ${this.bluemix.name}.wsgi -b 0.0.0.0:$PORT`;
		this.manifestConfig.memory = this.manifestConfig.memory || '128M';
		this.cfIgnoreContent = ['.pyc', '.egg-info'];
	}

	cleanUpPass() {
		if (this.manifestConfig && this.manifestConfig.env && Object.keys(this.manifestConfig.env).length < 1) {
			delete this.manifestConfig.env;
		}
		if (this.cfIgnoreContent) {
			this.cfIgnoreContent = this.cfIgnoreContent.join('\n');
		}
	}

	writing() {
		//skip writing files if platforms is specified via options and it doesn't include bluemix
		if (this.opts.platforms && !this.opts.platforms.includes('bluemix')) {
			return;
		}
		// write manifest.yml file
		this._writeHandlebarsFile('manifest_master.yml', 'manifest.yml', this.manifestConfig)

		// if cfIgnnoreContent exists, create/write .cfignore file
		if (this.cfIgnoreContent) {
			this.fs.write('.cfignore', this.cfIgnoreContent);
		}

		// create .bluemix directory for toolchain/devops related files
		this._writeHandlebarsFile('toolchain_master.yml', '.bluemix/toolchain.yml', {
			name: this.opts.toolchainName || this.name,
			org: this.opts.deploymentOrg || "{{organization}}",
			space: this.opts.deploymentSpace || "{{space}}",
			region: this.opts.deploymentRegion || "{{region}}",
			repoType: this.toolchainConfig.repoType,
			deployment: this.deployment
		});

		this._writeHandlebarsFile('deploy_master.json', '.bluemix/deploy.json', {
			deployment: this.deployment
		});

		this._writeHandlebarsFile('container_build.sh', '.bluemix/scripts/container_build.sh', {
			deployment: this.deployment
		});

		this._writeHandlebarsFile('kube_deploy.sh', '.bluemix/scripts/kube_deploy.sh', {
			deployment: this.deployment
		});

		if (Utils.sanitizeAlphaNumLowerCase(this.deployment.type) === "vsi") {
			this._writeHandlebarsFile('vsi_pipeline_master.yml', '.bluemix/pipeline.yml', {
				config: this.pipelineConfig,
				name: this.name,
				lowercaseName: Utils.sanitizeAlphaNumLowerCase(this.name),
				deployment: this.deployment
			});
		}
		else {
			this._writeHandlebarsFile('pipeline_master.yml', '.bluemix/pipeline.yml', {
				config: this.pipelineConfig,
				deployment: this.deployment
			});
		}

	}

	_writeHandlebarsFile(templateFile, destinationFile, data) {
		let template = this.fs.read(this.templatePath(templateFile));
		let compiledTemplate = Handlebars.compile(template);
		let output = compiledTemplate(data);
		this.fs.write(this.destinationPath(destinationFile), output);
	}
};
<|endoftext|>