<commit_before>var Erizo = Erizo || {};

Erizo.ChromeCanaryStack = function (spec) {
    "use strict";

    var that = {};

    that.pc_config = {
        "iceServers": []
    };

    that.mediaConstraints = {
        'mandatory': {
            'OfferToReceiveVideo': 'true',
            'OfferToReceiveAudio': 'true'
        }
    };

    that.peerConnection = new webkitRTCPeerConnection(that.pc_config);

    that.peerConnection.onicecandidate = function (event) {
        console.log("State: " + that.peerConnection.iceGatheringState);
        if (!event.candidate) {
            // At the moment, we do not renegotiate when new candidates
            // show up after the more flag has been false once.
            console.log("State: " + that.peerConnection.iceGatheringState);

            if (that.ices === undefined) {
              that.ices = 0;
            }
            that.ices++;
            console.log(that.ices);
            if (that.ices >=1 && that.moreIceComing) {
              that.moreIceComing = false;
              that.markActionNeeded();
            }
        } else {
            that.iceCandidateCount += 1;
        }
    };

    console.log("Created webkitRTCPeerConnnection with config \"" + JSON.stringify(that.pc_config) + "\".");

    /**
     * This function processes signalling messages from the other side.
     * @param {string} msgstring JSON-formatted string containing a ROAP message.
     */
    that.processSignalingMessage = function (msgstring) {
        // Offer: Check for glare and resolve.
        // Answer/OK: Remove retransmit for the msg this is an answer to.
        // Send back "OK" if this was an Answer.
        console.log('Activity on conn ' + that.sessionId);
        var msg = JSON.parse(msgstring), sd;
        that.incomingMessage = msg;

        if (that.state === 'new') {
            if (msg.messageType === 'OFFER') {
                // Initial offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'offer-sent') {
            if (msg.messageType === 'ANSWER') {

                var regExp = new RegExp(/m=video.*\r\n/g);

                var exp = msg.sdp.match(regExp);
                console.log(exp);

                msg.sdp = msg.sdp.replace(regExp, exp+"b=AS:50\r\n");

                sd = {
                    sdp: msg.sdp,
                    type: 'answer'
                };
                console.log("Received ANSWER: ", sd);
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));
                that.sendOK();
                that.state = 'established';

            } else if (msg.messageType === 'pr-answer') {
                sd = {
                    sdp: msg.sdp,
                    type: 'pr-answer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                // No change to state, and no response.
            } else if (msg.messageType === 'offer') {
                // Glare processing.
                that.error('Not written yet');
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'established') {
            if (msg.messageType === 'OFFER') {
                // Subsequent offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }
        }
    };

    /**
     * Adds a stream - this causes signalling to happen, if needed.
     * @param {MediaStream} stream The outgoing MediaStream to add.
     */
    that.addStream = function (stream) {
        that.peerConnection.addStream(stream);
        that.markActionNeeded();
    };

    /**
     * Removes a stream.
     * @param {MediaStream} stream The MediaStream to remove.
     */
    that.removeStream = function (stream) {
//        var i;
//        for (i = 0; i < that.peerConnection.localStreams.length; ++i) {
//            if (that.localStreams[i] === stream) {
//                that.localStreams[i] = null;
//            }
//        }
        that.markActionNeeded();
    };

    /**
     * Closes the connection.
     */
    that.close = function () {
        that.state = 'closed';
        that.peerConnection.close();
    };

    /**
     * Internal function: Mark that something happened.
     */
    that.markActionNeeded = function () {
        that.actionNeeded = true;
        that.doLater(function () {
            that.onstablestate();
        });
    };

    /**
     * Internal function: Do something later (not on this stack).
     * @param {function} what Callback to be executed later.
     */
    that.doLater = function (what) {
        // Post an event to myself so that I get called a while later.
        // (needs more JS/DOM info. Just call the processing function on a delay
        // for now.)
        window.setTimeout(what, 1);
    };

    /**
     * Internal function called when a stable state
     * is entered by the browser (to allow for multiple AddStream calls or
     * other interesting actions).
     * This function will generate an offer or answer, as needed, and send
     * to the remote party using our onsignalingmessage function.
     */
    that.onstablestate = function () {
        var mySDP;
        var roapMessage = {};
        if (that.actionNeeded) {
            if (that.state === 'new' || that.state === 'established') {
                // See if the current offer is the same as what we already sent.
                // If not, no change is needed.

                that.peerConnection.createOffer(function (sessionDescription) {

                    var regExp = new RegExp(/m=video.*\r\n/g);

                    var exp = sessionDescription.sdp.match(regExp);
                    console.log(exp);

                    sessionDescription.sdp = sessionDescription.sdp.replace(regExp, exp+"b=AS:50\r\n");

                    var newOffer = sessionDescription.sdp;

                    console.log("Changed",sessionDescription.sdp);

                    if (newOffer !== that.prevOffer) {

                        that.peerConnection.setLocalDescription(sessionDescription);

                        that.state = 'preparing-offer';
                        that.markActionNeeded();
                        return;
                    } else {
                        console.log('Not sending a new offer');
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'preparing-offer') {
                // Don't do anything until we have the ICE candidates.
                if (that.moreIceComing) {
                    return;
                }

                // Now able to send the offer we've already prepared.
                that.prevOffer = that.peerConnection.localDescription.sdp;
                console.log("Sending OFFER: ", that.prevOffer);
                //console.log('Sent SDP is ' + that.prevOffer);
                that.sendMessage('OFFER', that.prevOffer);
                // Not done: Retransmission on non-response.
                that.state = 'offer-sent';

            } else if (that.state === 'offer-received') {

                that.peerConnection.createAnswer(function (sessionDescription) {
                    that.peerConnection.setLocalDescription(sessionDescription);
                    that.state = 'offer-received-preparing-answer';

                    if (!that.iceStarted) {
                        var now = new Date();
                        console.log(now.getTime() + ': Starting ICE in responder');
                        that.iceStarted = true;
                    } else {
                        that.markActionNeeded();
                        return;
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'offer-received-preparing-answer') {
                if (that.moreIceComing) {
                    return;
                }

                mySDP = that.peerConnection.localDescription.sdp;

                that.sendMessage('ANSWER', mySDP);
                that.state = 'established';
            } else {
                that.error('Dazed and confused in state ' + that.state + ', stopping here');
            }
            that.actionNeeded = false;
        }
    };

    /**
     * Internal function to send an "OK" message.
     */
    that.sendOK = function () {
        that.sendMessage('OK');
    };

    /**
     * Internal function to send a signalling message.
     * @param {string} operation What operation to signal.
     * @param {string} sdp SDP message body.
     */
    that.sendMessage = function (operation, sdp) {
        var roapMessage = {};
        roapMessage.messageType = operation;
        roapMessage.sdp = sdp; // may be null or undefined
        if (operation === 'OFFER') {
            roapMessage.offererSessionId = that.sessionId;
            roapMessage.answererSessionId = that.otherSessionId; // may be null
            roapMessage.seq = ++that.sequenceNumber;
            // The tiebreaker needs to be neither 0 nor 429496725.
            roapMessage.tiebreaker = Math.floor(Math.random() * 429496723 + 1);
        } else {
            roapMessage.offererSessionId = that.incomingMessage.offererSessionId;
            roapMessage.answererSessionId = that.sessionId;
            roapMessage.seq = that.incomingMessage.seq;
        }
        that.onsignalingmessage(JSON.stringify(roapMessage));
    };

    /**
     * Internal something-bad-happened function.
     * @param {string} text What happened - suitable for logging.
     */
    that.error = function (text) {
        throw 'Error in RoapOnJsep: ' + text;
    };

    that.sessionId = ++RoapConnection.sessionId;
    that.sequenceNumber = 0; // Number of last ROAP message sent. Starts at 1.
    that.actionNeeded = false;
    that.iceStarted = false;
    that.moreIceComing = true;
    that.iceCandidateCount = 0;
    that.onsignalingmessage = spec.callback;

    that.peerConnection.onopen = function () {
        if (that.onopen) {
            that.onopen();
        }
    };

    that.peerConnection.onaddstream = function (stream) {
        if (that.onaddstream) {
            that.onaddstream(stream);
        }
    };

    that.peerConnection.onremovestream = function (stream) {
        if (that.onremovestream) {
            that.onremovestream(stream);
        }
    };

    // Variables that are part of the public interface of PeerConnection
    // in the 28 January 2012 version of the webrtc specification.
    that.onaddstream = null;
    that.onremovestream = null;
    that.state = 'new';
    // Auto-fire next events.
    that.markActionNeeded();
    return that;
};<commit_msg>Changes in bandwidth flag<commit_after>var Erizo = Erizo || {};

Erizo.ChromeCanaryStack = function (spec) {
    "use strict";

    var that = {};

    that.pc_config = {
        "iceServers": [{
            "url": "stun:stun.l.google.com:19302"
        }]
    };

    that.mediaConstraints = {
        'mandatory': {
            'OfferToReceiveVideo': 'true',
            'OfferToReceiveAudio': 'true'
        }
    };

    that.peerConnection = new webkitRTCPeerConnection(that.pc_config);

    that.peerConnection.onicecandidate = function (event) {
        console.log("PeerConnection: ", spec.session_id);
        if (!event.candidate) {
            // At the moment, we do not renegotiate when new candidates
            // show up after the more flag has been false once.
            console.log("State: " + that.peerConnection.iceGatheringState);

            if (that.ices === undefined) {
              that.ices = 0;
            }
            that.ices++;
            console.log(that.ices);
            if (that.ices >=1 && that.moreIceComing) {
              that.moreIceComing = false;
              that.markActionNeeded();
            }
        } else {
            that.iceCandidateCount += 1;
        }
    };

    console.log("Created webkitRTCPeerConnnection with config \"" + JSON.stringify(that.pc_config) + "\".");

    /**
     * This function processes signalling messages from the other side.
     * @param {string} msgstring JSON-formatted string containing a ROAP message.
     */
    that.processSignalingMessage = function (msgstring) {
        // Offer: Check for glare and resolve.
        // Answer/OK: Remove retransmit for the msg this is an answer to.
        // Send back "OK" if this was an Answer.
        console.log('Activity on conn ' + that.sessionId);
        var msg = JSON.parse(msgstring), sd;
        that.incomingMessage = msg;

        if (that.state === 'new') {
            if (msg.messageType === 'OFFER') {
                // Initial offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'offer-sent') {
            if (msg.messageType === 'ANSWER') {

                var regExp = new RegExp(/m=video.*\r\n/g);

                var exp = msg.sdp.match(regExp);
                console.log(exp);

                msg.sdp = msg.sdp.replace(regExp, exp+"b=AS:2000\r\n");

                sd = {
                    sdp: msg.sdp,
                    type: 'answer'
                };
                console.log("Received ANSWER: ", sd);
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));
                that.sendOK();
                that.state = 'established';

            } else if (msg.messageType === 'pr-answer') {
                sd = {
                    sdp: msg.sdp,
                    type: 'pr-answer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                // No change to state, and no response.
            } else if (msg.messageType === 'offer') {
                // Glare processing.
                that.error('Not written yet');
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }

        } else if (that.state === 'established') {
            if (msg.messageType === 'OFFER') {
                // Subsequent offer.
                sd = {
                    sdp: msg.sdp,
                    type: 'offer'
                };
                that.peerConnection.setRemoteDescription(new RTCSessionDescription(sd));

                that.state = 'offer-received';
                // Allow other stuff to happen, then reply.
                that.markActionNeeded();
            } else {
                that.error('Illegal message for this state: ' + msg.messageType + ' in state ' + that.state);
            }
        }
    };

    /**
     * Adds a stream - this causes signalling to happen, if needed.
     * @param {MediaStream} stream The outgoing MediaStream to add.
     */
    that.addStream = function (stream) {
        that.peerConnection.addStream(stream);
        that.markActionNeeded();
    };

    /**
     * Removes a stream.
     * @param {MediaStream} stream The MediaStream to remove.
     */
    that.removeStream = function (stream) {
//        var i;
//        for (i = 0; i < that.peerConnection.localStreams.length; ++i) {
//            if (that.localStreams[i] === stream) {
//                that.localStreams[i] = null;
//            }
//        }
        that.markActionNeeded();
    };

    /**
     * Closes the connection.
     */
    that.close = function () {
        that.state = 'closed';
        that.peerConnection.close();
    };

    /**
     * Internal function: Mark that something happened.
     */
    that.markActionNeeded = function () {
        that.actionNeeded = true;
        that.doLater(function () {
            that.onstablestate();
        });
    };

    /**
     * Internal function: Do something later (not on this stack).
     * @param {function} what Callback to be executed later.
     */
    that.doLater = function (what) {
        // Post an event to myself so that I get called a while later.
        // (needs more JS/DOM info. Just call the processing function on a delay
        // for now.)
        window.setTimeout(what, 1);
    };

    /**
     * Internal function called when a stable state
     * is entered by the browser (to allow for multiple AddStream calls or
     * other interesting actions).
     * This function will generate an offer or answer, as needed, and send
     * to the remote party using our onsignalingmessage function.
     */
    that.onstablestate = function () {
        var mySDP;
        var roapMessage = {};
        if (that.actionNeeded) {
            if (that.state === 'new' || that.state === 'established') {
                // See if the current offer is the same as what we already sent.
                // If not, no change is needed.

                that.peerConnection.createOffer(function (sessionDescription) {

                    var newOffer = sessionDescription.sdp;

                    console.log("Changed " + spec.session_id ,sessionDescription.sdp);

                    if (newOffer !== that.prevOffer) {

                        that.peerConnection.setLocalDescription(sessionDescription);

                        that.state = 'preparing-offer';
                        that.markActionNeeded();
                        return;
                    } else {
                        console.log('Not sending a new offer');
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'preparing-offer') {
                // Don't do anything until we have the ICE candidates.
                if (that.moreIceComing) {
                    return;
                }

                // Now able to send the offer we've already prepared.
                that.prevOffer = that.peerConnection.localDescription.sdp;
                console.log("Sending OFFER: ", that.prevOffer);
                //console.log('Sent SDP is ' + that.prevOffer);
                that.sendMessage('OFFER', that.prevOffer);
                // Not done: Retransmission on non-response.
                that.state = 'offer-sent';

            } else if (that.state === 'offer-received') {

                that.peerConnection.createAnswer(function (sessionDescription) {
                    that.peerConnection.setLocalDescription(sessionDescription);
                    that.state = 'offer-received-preparing-answer';

                    if (!that.iceStarted) {
                        var now = new Date();
                        console.log(now.getTime() + ': Starting ICE in responder');
                        that.iceStarted = true;
                    } else {
                        that.markActionNeeded();
                        return;
                    }

                }, null, that.mediaConstraints);

            } else if (that.state === 'offer-received-preparing-answer') {
                if (that.moreIceComing) {
                    return;
                }

                mySDP = that.peerConnection.localDescription.sdp;

                that.sendMessage('ANSWER', mySDP);
                that.state = 'established';
            } else {
                that.error('Dazed and confused in state ' + that.state + ', stopping here');
            }
            that.actionNeeded = false;
        }
    };

    /**
     * Internal function to send an "OK" message.
     */
    that.sendOK = function () {
        that.sendMessage('OK');
    };

    /**
     * Internal function to send a signalling message.
     * @param {string} operation What operation to signal.
     * @param {string} sdp SDP message body.
     */
    that.sendMessage = function (operation, sdp) {
        var roapMessage = {};
        roapMessage.messageType = operation;
        roapMessage.sdp = sdp; // may be null or undefined
        if (operation === 'OFFER') {
            roapMessage.offererSessionId = that.sessionId;
            roapMessage.answererSessionId = that.otherSessionId; // may be null
            roapMessage.seq = ++that.sequenceNumber;
            // The tiebreaker needs to be neither 0 nor 429496725.
            roapMessage.tiebreaker = Math.floor(Math.random() * 429496723 + 1);
        } else {
            roapMessage.offererSessionId = that.incomingMessage.offererSessionId;
            roapMessage.answererSessionId = that.sessionId;
            roapMessage.seq = that.incomingMessage.seq;
        }
        that.onsignalingmessage(JSON.stringify(roapMessage));
    };

    /**
     * Internal something-bad-happened function.
     * @param {string} text What happened - suitable for logging.
     */
    that.error = function (text) {
        throw 'Error in RoapOnJsep: ' + text;
    };

    that.sessionId = ++RoapConnection.sessionId;
    that.sequenceNumber = 0; // Number of last ROAP message sent. Starts at 1.
    that.actionNeeded = false;
    that.iceStarted = false;
    that.moreIceComing = true;
    that.iceCandidateCount = 0;
    that.onsignalingmessage = spec.callback;

    that.peerConnection.onopen = function () {
        if (that.onopen) {
            that.onopen();
        }
    };

    that.peerConnection.onaddstream = function (stream) {
        if (that.onaddstream) {
            that.onaddstream(stream);
        }
    };

    that.peerConnection.onremovestream = function (stream) {
        if (that.onremovestream) {
            that.onremovestream(stream);
        }
    };

    // Variables that are part of the public interface of PeerConnection
    // in the 28 January 2012 version of the webrtc specification.
    that.onaddstream = null;
    that.onremovestream = null;
    that.state = 'new';
    // Auto-fire next events.
    that.markActionNeeded();
    return that;
};<|endoftext|>


<commit_before>/*
 * grunt-html-build
 * https://github.com/spatools/grunt-html-build
 * Copyright (c) 2013 SPA Tools
 * Code below is licensed under MIT License
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

module.exports = function (grunt) {
    //#region Global Properties

    var // Init
        _ = grunt.util._,
        EOL = grunt.util.linefeed,
        path = require('path'),
        beautifier = require('js-beautify'),
        beautify = {
            js: beautifier.js,
            css: beautifier.css,
            html: beautifier.html
        },

        // Tags Regular Expressions
        regexTagStartTemplate = "<!--\\s*%parseTag%:(\\w+)\\s*(inline)?\\s*(optional)?\\s*([^\\s]*)\\s*-->", // <!-- build:{type} [inline] [optional] {name} --> {} required [] optional
        regexTagEndTemplate = "<!--\\s*\\/%parseTag%\\s*-->", // <!-- /build -->
        regexTagStart = "",
        regexTagEnd = "",
        isFileRegex = /\.(\w+){2,4}$/;

    //#endregion

    //#region Private Methods

    function getBuildTags(content) {
        var lines = content.replace(/\r\n/g, '\n').split(/\n/),
            tag = false,
            tags = [],
            last;

        lines.forEach(function (l) {
            var tagStart = l.match(new RegExp(regexTagStart)),
                tagEnd = new RegExp(regexTagEnd).test(l);

            if (tagStart) {
                tag = true;
                last = { type: tagStart[1], inline: !!tagStart[2], optional: !!tagStart[3], name: tagStart[4], lines: [] };
                tags.push(last);
            }

            // switch back tag flag when endbuild
            if (tag && tagEnd) {
                last.lines.push(l);
                tag = false;
            }

            if (tag && last) {
                last.lines.push(l);
            }
        });

        return tags;
    }
    function validateBlockWithName(tag, params) {
        var src = params[tag.type + "s"][tag.name];
        if (src) {
            var opt = {},
                files = src;

            if (_.isObject(src)) {
                opt = src;
                files = src.files;

                delete opt.files;
            }

            return grunt.file.expand(opt, files);
        }
    }
    function validateBlockAlways(tag) {
        return true;
    }

    function setTagRegexes(parseTag) {
        regexTagStart = regexTagStartTemplate.replace(/%parseTag%/, function () { return parseTag });
        regexTagEnd = regexTagEndTemplate.replace(/%parseTag%/, function () { return parseTag });
    }

    //#endregion

    //#region Processors Methods

    function createTemplateData(options, extend) {
        return {
            data: extend ? _.extend({}, options.data, extend) : options.data
        };
    }
    function processTemplate(template, options, extend) {
        return grunt.template.process(template, createTemplateData(options, extend));
    }
    function processHtmlTagTemplate(options, extend, inline) {
        var template = templates[options.type + (inline ? "-inline" : "")];
        return processTemplate(template, options, extend);
    }

    function processHtmlTag(options) {
        if (options.inline) {
            var content = options.files.map(grunt.file.read).join(EOL);
            return processHtmlTagTemplate(options, { content: content }, true);
        }
        else {
            return options.files.map(function (f) {
                var url = options.relative ? path.relative(options.dest, f) : f;
                url = url.replace(/\\/g, '/');

                if (options.prefix) {
                    url = path.join(options.prefix, url);
                }

                return processHtmlTagTemplate(options, { src: url });
            }).join(EOL);
        }
    }

    //#endregion

    //#region Processors / Validators / Templates

    var
        templates = {
            'script': '<script type="text/javascript" src="<%= src %>"></script>',
            'script-inline': '<script type="text/javascript"><%= content %></script>',
            'style': '<link type="text/css" rel="stylesheet" href="<%= src %>" />',
            'style-inline': '<style><%= content %></style>'
        },
        validators = {
            script: validateBlockWithName,
            style: validateBlockWithName,
            section: validateBlockWithName,

            process: validateBlockAlways,
            remove: validateBlockAlways,

            //base method
            validate: function (tag, params) {
                return validators[tag.type](tag, params);
            }
        },
        processors = {
            script: processHtmlTag,
            style: processHtmlTag,
            section: function (options) {
                return options.files.map(grunt.file.read).join(EOL);
            },

            process: function (options) {
                return options.lines
                                .map(function (l) { return processTemplate(l, options); })
                                .join(EOL)
                                .replace(new RegExp(regexTagStart), "")
                                .replace(new RegExp(regexTagEnd), "");
            },
            remove: function (options) {
                return "";
            },

            //base method
            transform: function (options) {
                return processors[options.type](options);
            }
        };

    //#endregion

    grunt.registerMultiTask('htmlbuild', "Grunt HTML Builder - Replace scripts and styles, Removes debug parts, append html partials, Template options", function () {
        var config = grunt.config(),
            params = this.options({
                beautify: false,
                logOptionals: false,
                relative: true,
                scripts: {},
                styles: {},
                sections: {},
                data: {},
                parseTag: 'build'
            });

        setTagRegexes(params.parseTag);

        this.files.forEach(function (file) {
            var dest = file.dest || "",
                destPath, content, tags;

            file.src.forEach(function (src) {
                if (params.replace) {
                    destPath = src;
                }
                else if (isFileRegex.test(dest)) {
                    destPath = dest;
                }
                else {
                    destPath = path.join(dest, path.basename(src));
                }

                content = grunt.util.normalizelf(grunt.file.read(src).toString());
                tags = getBuildTags(content);

                tags.forEach(function (tag) {
                    var raw = tag.lines.join(EOL),
                        result = "",
                        tagFiles = validators.validate(tag, params);

                    if (tagFiles) {
                        var options = _.extend({}, tag, {
                            data: _.extend({}, config, params.data),
                            files: tagFiles,
                            dest: dest,
                            prefix: params.prefix,
                            relative: params.relative
                        });

                        result = processors.transform(options);
                    }
                    else if (tag.optional) {
                        if (params.logOptionals)
                            grunt.log.error().error("Tag with type: '" + tag.type + "' and name: '" + tag.name + "' is not configured in your Gruntfile.js but is set optional, deleting block !");
                    }
                    else {
                        grunt.fail.warn("Tag with type '" + tag.type + "' and name: '" + tag.name + "' is not configured in your Gruntfile.js !");
                    }

                    content = content.replace(raw, function () { return result });
                });

                if (params.beautify) {
                    content = beautify.html(content, _.isObject(params.beautify) ? params.beautify : {});
                }

                // write the contents to destination
                grunt.file.write(destPath, content);
                grunt.log.ok("File " + destPath + " created !");
            });
        });
    });
};
<commit_msg>Add nested parameters<commit_after>/*
 * grunt-html-build
 * https://github.com/spatools/grunt-html-build
 * Copyright (c) 2013 SPA Tools
 * Code below is licensed under MIT License
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

module.exports = function (grunt) {
    //#region Global Properties

    var // Init
        _ = grunt.util._,
        EOL = grunt.util.linefeed,
        path = require('path'),
        beautifier = require('js-beautify'),
        beautify = {
            js: beautifier.js,
            css: beautifier.css,
            html: beautifier.html
        },

        // Tags Regular Expressions
        regexTagStartTemplate = "<!--\\s*%parseTag%:(\\w+)\\s*(inline)?\\s*(optional)?\\s*([^\\s]*)\\s*-->", // <!-- build:{type} [inline] [optional] {name} --> {} required [] optional
        regexTagEndTemplate = "<!--\\s*\\/%parseTag%\\s*-->", // <!-- /build -->
        regexTagStart = "",
        regexTagEnd = "",
        isFileRegex = /\.(\w+){2,4}$/;

    //#endregion

    //#region Private Methods

    function getBuildTags(content) {
        var lines = content.replace(/\r\n/g, '\n').split(/\n/),
            tag = false,
            tags = [],
            last;

        lines.forEach(function (l) {
            var tagStart = l.match(new RegExp(regexTagStart)),
                tagEnd = new RegExp(regexTagEnd).test(l);

            if (tagStart) {
                tag = true;
                last = { type: tagStart[1], inline: !!tagStart[2], optional: !!tagStart[3], name: tagStart[4], lines: [] };
                tags.push(last);
            }

            // switch back tag flag when endbuild
            if (tag && tagEnd) {
                last.lines.push(l);
                tag = false;
            }

            if (tag && last) {
                last.lines.push(l);
            }
        });

        return tags;
    }
    function validateBlockWithName(tag, params) {
        var src = params[tag.type + "s"];

	var keys = tag.name.split(".");
	var ln = keys.length;

	for(var i=0; i < ln; i++){ src = src[keys[i]]; }	// Search target

        if (src) {
            var opt = {},
                files = src;

            if (_.isObject(src)) {
                opt = src;
                files = src.files;

                delete opt.files;
            }

            return grunt.file.expand(opt, files);
        }
    }
    function validateBlockAlways(tag) {
        return true;
    }

    function setTagRegexes(parseTag) {
        regexTagStart = regexTagStartTemplate.replace(/%parseTag%/, function () { return parseTag });
        regexTagEnd = regexTagEndTemplate.replace(/%parseTag%/, function () { return parseTag });
    }

    //#endregion

    //#region Processors Methods

    function createTemplateData(options, extend) {
        return {
            data: extend ? _.extend({}, options.data, extend) : options.data
        };
    }
    function processTemplate(template, options, extend) {
        return grunt.template.process(template, createTemplateData(options, extend));
    }
    function processHtmlTagTemplate(options, extend, inline) {
        var template = templates[options.type + (inline ? "-inline" : "")];
        return processTemplate(template, options, extend);
    }

    function processHtmlTag(options) {
        if (options.inline) {
            var content = options.files.map(grunt.file.read).join(EOL);
            return processHtmlTagTemplate(options, { content: content }, true);
        }
        else {
            return options.files.map(function (f) {
                var url = options.relative ? path.relative(options.dest, f) : f;
                url = url.replace(/\\/g, '/');

                if (options.prefix) {
                    url = path.join(options.prefix, url);
                }

                return processHtmlTagTemplate(options, { src: url });
            }).join(EOL);
        }
    }

    //#endregion

    //#region Processors / Validators / Templates

    var
        templates = {
            'script': '<script type="text/javascript" src="<%= src %>"></script>',
            'script-inline': '<script type="text/javascript"><%= content %></script>',
            'style': '<link type="text/css" rel="stylesheet" href="<%= src %>" />',
            'style-inline': '<style><%= content %></style>'
        },
        validators = {
            script: validateBlockWithName,
            style: validateBlockWithName,
            section: validateBlockWithName,

            process: validateBlockAlways,
            remove: validateBlockAlways,

            //base method
            validate: function (tag, params) {
                return validators[tag.type](tag, params);
            }
        },
        processors = {
            script: processHtmlTag,
            style: processHtmlTag,
            section: function (options) {
                return options.files.map(grunt.file.read).join(EOL);
            },

            process: function (options) {
                return options.lines
                                .map(function (l) { return processTemplate(l, options); })
                                .join(EOL)
                                .replace(new RegExp(regexTagStart), "")
                                .replace(new RegExp(regexTagEnd), "");
            },
            remove: function (options) {
                return "";
            },

            //base method
            transform: function (options) {
                return processors[options.type](options);
            }
        };

    //#endregion

    grunt.registerMultiTask('htmlbuild', "Grunt HTML Builder - Replace scripts and styles, Removes debug parts, append html partials, Template options", function () {
        var config = grunt.config(),
            params = this.options({
                beautify: false,
                logOptionals: false,
                relative: true,
                scripts: {},
                styles: {},
                sections: {},
                data: {},
                parseTag: 'build'
            });

        setTagRegexes(params.parseTag);

        this.files.forEach(function (file) {
            var dest = file.dest || "",
                destPath, content, tags;

            file.src.forEach(function (src) {
                if (params.replace) {
                    destPath = src;
                }
                else if (isFileRegex.test(dest)) {
                    destPath = dest;
                }
                else {
                    destPath = path.join(dest, path.basename(src));
                }

                content = grunt.util.normalizelf(grunt.file.read(src).toString());
                tags = getBuildTags(content);

                tags.forEach(function (tag) {
                    var raw = tag.lines.join(EOL),
                        result = "",
                        tagFiles = validators.validate(tag, params);

                    if (tagFiles) {
                        var options = _.extend({}, tag, {
                            data: _.extend({}, config, params.data),
                            files: tagFiles,
                            dest: dest,
                            prefix: params.prefix,
                            relative: params.relative
                        });

                        result = processors.transform(options);
                    }
                    else if (tag.optional) {
                        if (params.logOptionals)
                            grunt.log.error().error("Tag with type: '" + tag.type + "' and name: '" + tag.name + "' is not configured in your Gruntfile.js but is set optional, deleting block !");
                    }
                    else {
                        grunt.fail.warn("Tag with type '" + tag.type + "' and name: '" + tag.name + "' is not configured in your Gruntfile.js !");
                    }

                    content = content.replace(raw, function () { return result });
                });

                if (params.beautify) {
                    content = beautify.html(content, _.isObject(params.beautify) ? params.beautify : {});
                }

                // write the contents to destination
                grunt.file.write(destPath, content);
                grunt.log.ok("File " + destPath + " created !");
            });
        });
    });
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  var coffeelint = require('coffeelint');

  grunt.registerMultiTask('coffeelint', 'Validate files with CoffeeLint', function() {

    var files = this.filesSrc;
    var options = this.options()
    var errorCount = 0;
    var warnCount = 0;

    files.forEach(function(file) {
      grunt.verbose.writeln('Linting ' + file + '...');

      var errors = coffeelint.lint(grunt.file.read(file), options);

      if (!errors.length) {
        grunt.verbose.ok();
      } else {
        errors.forEach(function(error) {
          var status;

          if (error.level === 'error') {
            errorCount += 1;
            status = "[error]".red;
          } else if (error.level === 'warn') {
            warnCount += 1;
            status = "[warn]".yellow;
          } else {
            return;
          }

          grunt.log.writeln(status + ' ' + file + ':' + error.lineNumber + ' ' + error.message + ' (' + error.rule + ')');
        });
      }
    });

    if (errorCount) {
      return false;
    }

    if (!warnCount) {
      grunt.log.ok(files.length + ' file' + (files.length === 1 ? '' : 's') + ' lint free.');
    }
  });
};
<commit_msg>missing semicolon<commit_after>module.exports = function(grunt) {
  var coffeelint = require('coffeelint');

  grunt.registerMultiTask('coffeelint', 'Validate files with CoffeeLint', function() {

    var files = this.filesSrc;
    var options = this.options();
    var errorCount = 0;
    var warnCount = 0;

    files.forEach(function(file) {
      grunt.verbose.writeln('Linting ' + file + '...');

      var errors = coffeelint.lint(grunt.file.read(file), options);

      if (!errors.length) {
        grunt.verbose.ok();
      } else {
        errors.forEach(function(error) {
          var status;

          if (error.level === 'error') {
            errorCount += 1;
            status = "[error]".red;
          } else if (error.level === 'warn') {
            warnCount += 1;
            status = "[warn]".yellow;
          } else {
            return;
          }

          grunt.log.writeln(status + ' ' + file + ':' + error.lineNumber + ' ' + error.message + ' (' + error.rule + ')');
        });
      }
    });

    if (errorCount) {
      return false;
    }

    if (!warnCount) {
      grunt.log.ok(files.length + ' file' + (files.length === 1 ? '' : 's') + ' lint free.');
    }
  });
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
	'use strict';

	grunt.registerMultiTask('iisexpress', 'Start an IIS Express process.', function() {
		var _ = grunt.util._;
		var options = this.options({
			cmd: 'c:/program files/iis express/iisexpress.exe',
			killOn: '',
			killOnExit: true,
			open: false,
			openPath: '/',
			openUrl: null,
			verbose: false
		});
		var killed = false;

		// If no entry point defined: run in the current dir
		if (options.config === undefined && options.site === undefined &&
			options.siteid === undefined && options.path === undefined) {
			options.path = '.';
		}

		// Convert options to command line parameter format
		var args = _.map(_.pairs(_.omit(options, ['cmd', 'killOn', 'killOnExit', 'open', 'openPath', 'openUrl', 'verbose'])), function(option) {
			if (option[0] == 'path') {
				option[1] = require('path').resolve(option[1]);
			}
			return '-' + option[0] + ':' + option[1];
		});

		var spawnOptions = {
			detached: true
		};

		// Start up IIS Express with the specified arguments
		var spawn = grunt.util.spawn({
			cmd: options.cmd,
			args: args,
			opts: spawnOptions
		}, function(error, result, code) {
			grunt.event.emit('iisexpress.done', error, result, code);
		});

		if (options.verbose===true) {
			spawn.stdout.on('data', function (data) {
				grunt.log.write(data.toString());
			});
		}

		spawn.stderr.on('data', function (data) {
			grunt.warn(data.toString());
		});

		grunt.log.ok('Started IIS Express.');

		if (options.open===true) {
			if (!options.port && !options.openUrl) {
				grunt.fail.fatal('Must specify port or openUrl when open==true');
			}
			var url = options.openUrl || 'http://localhost:' + options.port + options.openPath;
			grunt.log.writeln('opening', url);

			var done = this.async();
			require('open')(url, function() {
				done();
			});
		}

		if (options.killOn !== '') {
			// Register event listener to use to kill spawned process
			grunt.event.on(options.killOn, kill);
		}

		if (options.killOnExit===true) {
			var readLine = require('readline');

			var rl = readLine.createInterface({
				input: process.stdin,
				output: process.stdout
			});

			rl.on('SIGINT', function () {
				process.emit('SIGINT');
			});

			process.on('exit', kill);
			process.on('SIGINT', killAndExit);
			process.on('SIGHUP', killAndExit);
			process.on('SIGBREAK', killAndExit);
		}

		function kill() {
			if (killed) {
				return;
			}
			if (options.verbose) {
				grunt.log.write('Stopping IIS Express..');
			}
			spawn.kill();
			killed = true;
			if (options.verbose) {
				grunt.log.write(' ');
				grunt.log.ok();
			}
		}

		function killAndExit() {
			kill();
			process.exit();
		}
	});
};
<commit_msg>Add keepalive option/flag (like in grunt-contrib-connect)<commit_after>module.exports = function(grunt) {
	'use strict';

	grunt.registerMultiTask('iisexpress', 'Start an IIS Express process.', function() {
		var _ = grunt.util._;
		var options = this.options({
			cmd: 'c:/program files/iis express/iisexpress.exe',
			keepalive: false,
			killOn: '',
			killOnExit: true,
			open: false,
			openPath: '/',
			openUrl: null,
			verbose: false
		});
		var killed = false;

		// keepalive can be an option or a flag (eg 'iisexpress:dist:keepalive')
		var keepAlive = this.flags.keepalive || options.keepalive;

		// If no entry point defined: run in the current dir
		if (options.config === undefined && options.site === undefined &&
			options.siteid === undefined && options.path === undefined) {
			options.path = '.';
		}

		// Convert options to command line parameter format
		var args = _.map(_.pairs(_.omit(options, ['cmd', 'keepalive', 'killOn', 'killOnExit', 'open', 'openPath', 'openUrl', 'verbose'])), function(option) {
			if (option[0] == 'path') {
				option[1] = require('path').resolve(option[1]);
			}
			return '-' + option[0] + ':' + option[1];
		});

		var spawnOptions = {
			detached: true
		};

		// Start up IIS Express with the specified arguments
		var spawn = grunt.util.spawn({
			cmd: options.cmd,
			args: args,
			opts: spawnOptions
		}, function(error, result, code) {
			grunt.event.emit('iisexpress.done', error, result, code);
		});

		if (options.verbose===true) {
			spawn.stdout.on('data', function (data) {
				grunt.log.write(data.toString());
			});
		}

		spawn.stderr.on('data', function (data) {
			grunt.warn(data.toString());
		});

		grunt.log.ok('Started IIS Express.');

		if (options.open===true) {
			if (!options.port && !options.openUrl) {
				grunt.fail.fatal('Must specify port or openUrl when open==true');
			}
			var url = options.openUrl || 'http://localhost:' + options.port + options.openPath;
			grunt.log.writeln('opening', url);

			var done = this.async();
			require('open')(url, function() {
				if (!keepAlive) {
					done();
				} else {
					grunt.log.writeln('Waiting forever...');
				}
			});
		}

		if (keepAlive && !done) {
			var waitForever = this.async(); // grunt will not finish the task
			grunt.log.writeln('Waiting forever...');
		}

		if (options.killOn !== '') {
			// Register event listener to use to kill spawned process
			grunt.event.on(options.killOn, kill);
		}

		if (options.killOnExit===true) {
			var readLine = require('readline');

			var rl = readLine.createInterface({
				input: process.stdin,
				output: process.stdout
			});

			rl.on('SIGINT', function () {
				process.emit('SIGINT');
			});

			process.on('exit', kill);
			process.on('SIGINT', killAndExit);
			process.on('SIGHUP', killAndExit);
			process.on('SIGBREAK', killAndExit);
		}

		function kill() {
			if (killed) {
				return;
			}
			if (options.verbose) {
				grunt.log.write('Stopping IIS Express..');
			}
			spawn.kill();
			killed = true;
			if (options.verbose) {
				grunt.log.write(' ');
				grunt.log.ok();
			}
		}

		function killAndExit() {
			kill();
			process.exit();
		}
	});
};
<|endoftext|>


<commit_before>'use strict';

class ElasticSliderCore {
    constructor(el, options) {

        // Public properties
        // ====================================================================
        this.NAMESPACE = 'ElasticSlider';
        this.options = this._filterOptions(options);
        this.CLASS_NAME_LIST = {
            'container': `${this.NAMESPACE}-container`,
            'item': `${this.NAMESPACE}-item`,
            'itemActive': `${this.NAMESPACE}-item--isActive`,
            'itemClone': `${this.NAMESPACE}-item--clone`,
        }
        this._properties = new Map();

        // Class requirement pass check
        // ====================================================================
        this._checkClassRequirements(el);

        // Private properties
        // ====================================================================
        this._animationFunctionHash = {};
        this._slideCount = null;
        this._initialAnimationDelay = 50; // ms in which animation begins
        this._startAnimationUserCallback = () => {};
        this._endAnimationUserCallback = () => {};

        // Create animations
        // ====================================================================
        this._createAnimationFunctions();

        // Elements
        // ====================================================================
        this.elementList = {};
        this.elementList.slider = el;
        this.elementList.containerEl = this.elementList.slider.querySelector(`.${this.CLASS_NAME_LIST.container}`);
        this.elementList.slideArr = this.elementList.containerEl.children;
        this.elementList.slideActiveEl = this.elementList.slideArr[this.options.activeSlide - 1];
        this.elementList.cloneEl = null;
        this._slideCount = this.elementList.slideArr.length;

        // Run methods
        // ====================================================================
        this.setProp('activeSlideIndex', this.options.activeSlide - 1);
        this.setProp('nextActiveSlideIndex', this.options.activeSlide);
        this.setProp('isAnimating', false);

        // DOM manipulation
        // ====================================================================
        this.elementList.slider.classList.add(this.NAMESPACE);

        for (let i = 0; i < this.elementList.slideArr.length; i++) {
            let itemEl = this.elementList.slideArr[i]

            itemEl.classList.add(this.CLASS_NAME_LIST.item);
        }

        this._setActiveSlide(this.getProp('activeSlideIndex'));

        this._addCustomAnimationFunctions();
    }

    // ========================================================================
    // Public methods
    // ========================================================================
    // May be useful in future. No events to remove.
    destroy() {
        for (let k in this.CLASS_NAME_LIST) {
            // Only remove sub-module classes that begin with "item"
            // The other items aren't added via the core plugin, so don't
            // remove them.
            if (k.indexOf('item') !== 0) continue;

            let elementList = this.elementList.slider.querySelectorAll(`.${this.CLASS_NAME_LIST[k]}`);

            if (elementList.length) {
                // Loop through elements and remove classes
                for (let i = 0; i < elementList.length; i++) {
                    let element = elementList[i];
                    element.classList.remove(`${this.CLASS_NAME_LIST[k]}`);
                }
            }
        }

        this.elementList.slider.classList.remove(this.NAMESPACE);
    }

    toSlide(params = {}) {
        let {
            index,
            animation,
            startAnimationCallback,
            endAnimationCallback
        } = params;

        // Fallback
        // Allow for `params` to be a number.
        if (this._typeTest('number', params)) {
            params = { index: params };
            index = params.index;
        }
        else if (!this._typeTest('object', params)) {
            console.error('ElasticSlider.toSlide: params is expected to be an object. Eg. {index: 5}');
        }

        // `animation` fallback
        if (!this._typeTest('string', animation)) {
            animation = this.options.animation;
        }

        // Don't animate to the same slide
        // Don't animate while animating
        if (
            index === this.getProp('activeSlideIndex')
            || this.getProp('isAnimating') === true
        ) {
            return;
        };

        // Handle invalid index values
        if (!this._typeTest('number', index) || index > this._slideCount - 1) {
            index = 0;
        } else if (index < 0) {
            index = this._slideCount - 1;
        };

        // pause
        this._toggleActiveVideo(this.elementList.slideActiveEl.querySelectorAll('iframe'), false);

        this._createTransitionEl(index);
        this.setProp('nextActiveSlideIndex', index);
        this.setProp('isAnimating', true);

        // Set callback animations
        if (this._typeTest('function', startAnimationCallback)) {
            this._startAnimationUserCallback = startAnimationCallback;
        }

        if (this._typeTest('function', endAnimationCallback)) {
            this._endAnimationUserCallback = endAnimationCallback;
        }

        if (this._typeTest('string', animation)) {
            this._startAnimationUserCallback();
            this._animationFunctionHash[animation](this, index + 1)
        } else {
            this._endSlide(index);
        }
    }

    animationInit(cb) {
        if (this._typeTest('function', cb)) {
            // Run the method in context of the class
            cb = cb.bind(this);
            cb();
        }
    }

    animationStart(cb) {
        let self = this;

        // Defer
        window.setTimeout(() => {
            if (this._typeTest('function', cb)) {
                // Run the method in context of the class
                cb = cb.bind(self);
                cb();
            }
        }, this._initialAnimationDelay);
    }

    animationEnd(duration = 100, cb) {
        let self = this;
        let totalDuration = (duration) + this._initialAnimationDelay;

        window.setTimeout(() => {
            if (this._typeTest('function', cb)) {
                // Run the method in context of the class
                cb = cb.bind(self);
                cb();
            }

            self._endSlide();
        }, totalDuration);
    }

    addAnimationFunction(name, func) {
        if (this._typeTest('string', name) && this._typeTest('function', func)) {
            this._animationFunctionHash[name] = func.bind(this);
        } else {
            console.error('ElasticSlider.addAnimationFunction: Parameters must be name<String>, func<Function>');
        }
    }

    getElement(elName) {
        return this.elementList[elName];
    }

    getProp(propName) {
        return this._properties.get(propName);
    }

    setProp(propName, val) {
        this._properties.set(propName, val);
    }

    removeProp(propName, val) {
        this._properties.delete(propName);
    }

    // ========================================================================
    // Private methods
    // ========================================================================
    _filterOptions(o = {}) {
        if (!this._typeTest('number', o.activeSlide)) o.activeSlide = 1;
        if (!this._typeTest('string', o.animation)) o.animation = 'slide';

        return o;
    }

    _checkClassRequirements(el) {
        let requiredElementsArr = [
            this.CLASS_NAME_LIST.container
        ];

        requiredElementsArr.forEach((item) => {
            let element = el.querySelector(`.${item}`);

            if (!element) {
                throw `Required element .${item} missing. Please use an ElasticSlider UI plugin or read the documentation.`;
            }
        });
    }

    _endSlide() {
        this._setActiveSlide();
        this.setProp('isAnimating', false);

        // Remove clone
        this.elementList.containerEl.removeChild(this.elementList.cloneEl);
        this.elementList.cloneEl = null;

        // `toSlide` callback
        this._endAnimationUserCallback();

        // Play any videos
        this._toggleActiveVideo(this.elementList.slideActiveEl.querySelectorAll('iframe'), true);

        // Reset callbacks
        this._startAnimationUserCallback = () => {};
        this._endAnimationUserCallback = () => {};
    }

    _setActiveSlide(index = this.getProp('nextActiveSlideIndex')) {
        // Remove the active class name from all elements
        for (let i = 0; i < this.elementList.slideArr.length; i++) {
            let slide = this.elementList.slideArr[i];

            slide.classList.remove(this.CLASS_NAME_LIST.itemActive);
        }

        this.elementList.slideActiveEl = this.elementList.slideArr[index];
        this.elementList.slideActiveEl.classList.add(this.CLASS_NAME_LIST.itemActive);
        // Set active index and remove target item active index
        this.setProp('activeSlideIndex', index);
        this.setProp('nextActiveSlideIndex', null);
    }

    _createTransitionEl(index) {
        if (this.elementList.cloneEl) {
            this.elementList.containerEl.removeChild(this.elementList.cloneEl);
        }

        // Create el
        this.elementList.cloneEl = this.elementList.slideArr[index].cloneNode(true);
        this.elementList.cloneEl.classList.add(this.CLASS_NAME_LIST.itemClone)

        // Add to dom
        this.elementList.containerEl.appendChild(this.elementList.cloneEl);
    }

    // Animations. These are the default animations. Any extra animations
    // should be added via the external add method
    _createAnimationFunctions() {
        this.addAnimationFunction('fade', () =>  {
            this.animationInit(() => {
                this.elementList.cloneEl.classList.add(`${this.NAMESPACE}-item--animateFadeInit`);
            });

            this.animationStart(() => {
                this.elementList.cloneEl.classList.add(`${this.NAMESPACE}-item--animateFadeStart`);
            });

            this.animationEnd(600);
        });

        this.addAnimationFunction('slide', () => {
            let direction = null; // Determines which direction to slide
            let animationDirection = this.getProp('animationDirection');
            let nextActiveSlideIndex = this.getProp('nextActiveSlideIndex')
            let activeSlideIndex = this.getProp('activeSlideIndex')

            // If an explicit direction has been set
            if (animationDirection) {
                direction = animationDirection;
            }
            // Otherwise greater index means next
            else if (nextActiveSlideIndex > activeSlideIndex) {
                direction = 'next';
            }
            else {
                direction = 'prev';
            };

            // Capitalise direction for class cammel case
            direction = direction.substring(0, 1).toUpperCase() + direction.substring(1);

            this.animationInit(() => {
                this.elementList.cloneEl.classList.add(
                    `${this.NAMESPACE}-item--animate${direction}SlideInit`
                );
                this.elementList.slideActiveEl.classList.add(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideInit`
                );
            });

            this.animationStart(() => {
                this.elementList.cloneEl.classList.add(
                    `${this.NAMESPACE}-item--animate${direction}SlideStart`
                );
                this.elementList.slideActiveEl.classList.add(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideStart`
                );
            });

            this.animationEnd(1000, () => {
                this.elementList.slideActiveEl.classList.remove(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideInit`
                );
                this.elementList.slideActiveEl.classList.remove(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideStart`
                );

                // Don't allow this to be cached permanently
                this.removeProp('animationDirection');
            });
        });
    }

    // Allows for animations functions to be inserted from an external source
    _addCustomAnimationFunctions() {
        let customAnimationMap = window.elasticSliderAnimationMap;

        if (this._typeTest('object', customAnimationMap)) {
            for (let name in customAnimationMap) {
                this.addAnimationFunction(name, customAnimationMap[name]);
            }
        }
    }

    _typeTest(type, item) {
        let pass = false;

        switch (type) {
            case 'object':
                if (
                    typeof item === 'object' &&
                    item !== null &&
                    typeof item.length === 'undefined'
                ) {
                    pass = true;
                }
                break;

            case 'array':
                if (
                    typeof item === 'object' &&
                    item !== null &&
                    typeof item.length === 'number'
                ) {
                    pass = true;
                }
                break;

            case 'function':
                if (typeof item === 'function') {
                    pass = true;
                }
                break;

            case 'string':
                if (typeof item === 'string') {
                    pass = true;
                }
                break;

            case 'boolean':
                if (typeof item === 'boolean') {
                    pass = true;
                }
                break;

            case 'number':
                if (typeof item === 'number' && !isNaN(item)) {
                    pass = true;
                }
                break;
        }

        return pass;
    }

    // Play/pause youtube videos
    _toggleActiveVideo(elArr, enableBool) {
        var func = enableBool === true ? 'playVideo' : 'pauseVideo';

        // If elements have been passed through
        if (elArr.length) {
            // Run a play/pause on all videos in the slide
            for (var i = 0; i < elArr.length; i++) {
                if (elArr[i].src.indexOf('enablejsapi=true') !== -1) {
                    // Send the play/pause command
                    elArr[i].contentWindow.postMessage(
                        '{"event":"command","func":"' + func + '","args":""}', '*'
                    );
                }
            }
        }
    }
}

// Enable module support
if (typeof module === 'object' && module.exports) {
    module.exports = ElasticSliderCore;
} else {
    let obj = this || window;

    obj.ElasticSliderCore = ElasticSliderCore;
}
<commit_msg>replace relevant `let` declarations with `const`<commit_after>'use strict';

class ElasticSliderCore {
    constructor(el, options) {

        // Public properties
        // ====================================================================
        this.NAMESPACE = 'ElasticSlider';
        this.options = this._filterOptions(options);
        this.CLASS_NAME_LIST = {
            'container': `${this.NAMESPACE}-container`,
            'item': `${this.NAMESPACE}-item`,
            'itemActive': `${this.NAMESPACE}-item--isActive`,
            'itemClone': `${this.NAMESPACE}-item--clone`,
        }
        this._properties = new Map();

        // Class requirement pass check
        // ====================================================================
        this._checkClassRequirements(el);

        // Private properties
        // ====================================================================
        this._animationFunctionHash = {};
        this._slideCount = null;
        this._initialAnimationDelay = 50; // ms in which animation begins
        this._startAnimationUserCallback = () => {};
        this._endAnimationUserCallback = () => {};

        // Create animations
        // ====================================================================
        this._createAnimationFunctions();

        // Elements
        // ====================================================================
        this.elementList = {};
        this.elementList.slider = el;
        this.elementList.containerEl = this.elementList.slider.querySelector(`.${this.CLASS_NAME_LIST.container}`);
        this.elementList.slideArr = this.elementList.containerEl.children;
        this.elementList.slideActiveEl = this.elementList.slideArr[this.options.activeSlide - 1];
        this.elementList.cloneEl = null;
        this._slideCount = this.elementList.slideArr.length;

        // Run methods
        // ====================================================================
        this.setProp('activeSlideIndex', this.options.activeSlide - 1);
        this.setProp('nextActiveSlideIndex', this.options.activeSlide);
        this.setProp('isAnimating', false);

        // DOM manipulation
        // ====================================================================
        this.elementList.slider.classList.add(this.NAMESPACE);

        for (let i = 0; i < this.elementList.slideArr.length; i++) {
            let itemEl = this.elementList.slideArr[i]

            itemEl.classList.add(this.CLASS_NAME_LIST.item);
        }

        this._setActiveSlide(this.getProp('activeSlideIndex'));

        this._addCustomAnimationFunctions();
    }

    // ========================================================================
    // Public methods
    // ========================================================================
    // May be useful in future. No events to remove.
    destroy() {
        for (let k in this.CLASS_NAME_LIST) {
            // Only remove sub-module classes that begin with "item"
            // The other items aren't added via the core plugin, so don't
            // remove them.
            if (k.indexOf('item') !== 0) continue;

            const elementList = this.elementList.slider.querySelectorAll(`.${this.CLASS_NAME_LIST[k]}`);

            if (elementList.length) {
                // Loop through elements and remove classes
                for (let i = 0; i < elementList.length; i++) {
                    let element = elementList[i];
                    element.classList.remove(`${this.CLASS_NAME_LIST[k]}`);
                }
            }
        }

        this.elementList.slider.classList.remove(this.NAMESPACE);
    }

    toSlide(params = {}) {
        let {
            index,
            animation,
            startAnimationCallback,
            endAnimationCallback
        } = params;

        // Fallback
        // Allow for `params` to be a number.
        if (this._typeTest('number', params)) {
            params = { index: params };
            index = params.index;
        }
        else if (!this._typeTest('object', params)) {
            console.error('ElasticSliderCore.toSlide: params is expected to be an object. Eg. {index: 5}');
        }

        // `animation` fallback
        if (!this._typeTest('string', animation)) {
            animation = this.options.animation;
        }

        // Don't animate to the same slide
        // Don't animate while animating
        if (
            index === this.getProp('activeSlideIndex')
            || this.getProp('isAnimating') === true
        ) {
            return;
        };

        // Handle invalid index values
        if (!this._typeTest('number', index) || index > this._slideCount - 1) {
            index = 0;
        } else if (index < 0) {
            index = this._slideCount - 1;
        };

        // pause
        this._toggleActiveVideo(this.elementList.slideActiveEl.querySelectorAll('iframe'), false);

        this._createTransitionEl(index);
        this.setProp('nextActiveSlideIndex', index);
        this.setProp('isAnimating', true);

        // Set callback animations
        if (this._typeTest('function', startAnimationCallback)) {
            this._startAnimationUserCallback = startAnimationCallback;
        }

        if (this._typeTest('function', endAnimationCallback)) {
            this._endAnimationUserCallback = endAnimationCallback;
        }

        if (this._typeTest('string', animation)) {
            this._startAnimationUserCallback();
            this._animationFunctionHash[animation](this, index + 1)
        } else {
            this._endSlide(index);
        }
    }

    animationInit(cb) {
        if (this._typeTest('function', cb)) {
            // Run the method in context of the class
            cb = cb.bind(this);
            cb();
        }
    }

    animationStart(cb) {
        // Defer
        window.setTimeout(() => {
            if (this._typeTest('function', cb)) {
                // Run the method in context of the class
                cb = cb.bind(this);
                cb();
            }
        }, this._initialAnimationDelay);
    }

    animationEnd(duration = 100, cb) {
        const totalDuration = (duration) + this._initialAnimationDelay;

        window.setTimeout(() => {
            if (this._typeTest('function', cb)) {
                // Run the method in context of the class
                cb = cb.bind(this);
                cb();
            }

            this._endSlide();
        }, totalDuration);
    }

    addAnimationFunction(name, func) {
        if (this._typeTest('string', name) && this._typeTest('function', func)) {
            this._animationFunctionHash[name] = func.bind(this);
        } else {
            console.error('ElasticSliderCore.addAnimationFunction: Parameters must be name<String>, func<Function>');
        }
    }

    getElement(elName) {
        return this.elementList[elName];
    }

    getProp(propName) {
        return this._properties.get(propName);
    }

    setProp(propName, val) {
        this._properties.set(propName, val);
    }

    removeProp(propName, val) {
        this._properties.delete(propName);
    }

    // ========================================================================
    // Private methods
    // ========================================================================
    _filterOptions(o = {}) {
        if (!this._typeTest('number', o.activeSlide)) o.activeSlide = 1;
        if (!this._typeTest('string', o.animation)) o.animation = 'slide';

        return o;
    }

    _checkClassRequirements(el) {
        const requiredElementsArr = [
            this.CLASS_NAME_LIST.container
        ];

        requiredElementsArr.forEach((item) => {
            const element = el.querySelector(`.${item}`);

            if (!element) {
                throw `Required element .${item} missing. Please use an ElasticSlider UI plugin or read the documentation.`;
            }
        });
    }

    _endSlide() {
        this._setActiveSlide();
        this.setProp('isAnimating', false);

        // Remove clone
        this.elementList.containerEl.removeChild(this.elementList.cloneEl);
        this.elementList.cloneEl = null;

        // `toSlide` callback
        this._endAnimationUserCallback();

        // Play any videos
        this._toggleActiveVideo(this.elementList.slideActiveEl.querySelectorAll('iframe'), true);

        // Reset callbacks
        this._startAnimationUserCallback = () => {};
        this._endAnimationUserCallback = () => {};
    }

    _setActiveSlide(index = this.getProp('nextActiveSlideIndex')) {
        // Remove the active class name from all elements
        for (let i = 0; i < this.elementList.slideArr.length; i++) {
            const slide = this.elementList.slideArr[i];

            slide.classList.remove(this.CLASS_NAME_LIST.itemActive);
        }

        this.elementList.slideActiveEl = this.elementList.slideArr[index];
        this.elementList.slideActiveEl.classList.add(this.CLASS_NAME_LIST.itemActive);
        // Set active index and remove target item active index
        this.setProp('activeSlideIndex', index);
        this.setProp('nextActiveSlideIndex', null);
    }

    _createTransitionEl(index) {
        if (this.elementList.cloneEl) {
            this.elementList.containerEl.removeChild(this.elementList.cloneEl);
        }

        // Create el
        this.elementList.cloneEl = this.elementList.slideArr[index].cloneNode(true);
        this.elementList.cloneEl.classList.add(this.CLASS_NAME_LIST.itemClone)

        // Add to dom
        this.elementList.containerEl.appendChild(this.elementList.cloneEl);
    }

    // Animations. These are the default animations. Any extra animations
    // should be added via the external add method
    _createAnimationFunctions() {
        this.addAnimationFunction('fade', () =>  {
            this.animationInit(() => {
                this.elementList.cloneEl.classList.add(`${this.NAMESPACE}-item--animateFadeInit`);
            });

            this.animationStart(() => {
                this.elementList.cloneEl.classList.add(`${this.NAMESPACE}-item--animateFadeStart`);
            });

            this.animationEnd(600);
        });

        this.addAnimationFunction('slide', () => {
            let direction = null; // Determines which direction to slide
            const animationDirection = this.getProp('animationDirection');
            const nextActiveSlideIndex = this.getProp('nextActiveSlideIndex')
            const activeSlideIndex = this.getProp('activeSlideIndex')

            // If an explicit direction has been set
            if (animationDirection) {
                direction = animationDirection;
            }
            // Otherwise greater index means next
            else if (nextActiveSlideIndex > activeSlideIndex) {
                direction = 'next';
            }
            else {
                direction = 'prev';
            };

            // Capitalise direction for class cammel case
            direction = direction.substring(0, 1).toUpperCase() + direction.substring(1);

            this.animationInit(() => {
                this.elementList.cloneEl.classList.add(
                    `${this.NAMESPACE}-item--animate${direction}SlideInit`
                );
                this.elementList.slideActiveEl.classList.add(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideInit`
                );
            });

            this.animationStart(() => {
                this.elementList.cloneEl.classList.add(
                    `${this.NAMESPACE}-item--animate${direction}SlideStart`
                );
                this.elementList.slideActiveEl.classList.add(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideStart`
                );
            });

            this.animationEnd(1000, () => {
                this.elementList.slideActiveEl.classList.remove(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideInit`
                );
                this.elementList.slideActiveEl.classList.remove(
                    `${this.NAMESPACE}-item--animateActive${direction}SlideStart`
                );

                // Don't allow this to be cached permanently
                this.removeProp('animationDirection');
            });
        });
    }

    // Allows for animations functions to be inserted from an external source
    _addCustomAnimationFunctions() {
        const customAnimationMap = window.elasticSliderAnimationMap;

        if (this._typeTest('object', customAnimationMap)) {
            for (let name in customAnimationMap) {
                this.addAnimationFunction(name, customAnimationMap[name]);
            }
        }
    }

    _typeTest(type, item) {
        let pass = false;

        switch (type) {
            case 'object':
                if (
                    typeof item === 'object' &&
                    item !== null &&
                    typeof item.length === 'undefined'
                ) {
                    pass = true;
                }
                break;

            case 'array':
                if (
                    typeof item === 'object' &&
                    item !== null &&
                    typeof item.length === 'number'
                ) {
                    pass = true;
                }
                break;

            case 'function':
                if (typeof item === 'function') {
                    pass = true;
                }
                break;

            case 'string':
                if (typeof item === 'string') {
                    pass = true;
                }
                break;

            case 'boolean':
                if (typeof item === 'boolean') {
                    pass = true;
                }
                break;

            case 'number':
                if (typeof item === 'number' && !isNaN(item)) {
                    pass = true;
                }
                break;
        }

        return pass;
    }

    // Play/pause youtube videos
    _toggleActiveVideo(elArr, enableBool) {
        const func = enableBool === true ? 'playVideo' : 'pauseVideo';

        // If elements have been passed through
        if (elArr.length) {
            // Run a play/pause on all videos in the slide
            for (let i = 0; i < elArr.length; i++) {
                if (elArr[i].src.indexOf('enablejsapi=true') !== -1) {
                    // Send the play/pause command
                    elArr[i].contentWindow.postMessage(
                        '{"event":"command","func":"' + func + '","args":""}', '*'
                    );
                }
            }
        }
    }
}

// Enable module support
if (typeof module === 'object' && module.exports) {
    module.exports = ElasticSliderCore;
} else {
    const obj = this || window;

    obj.ElasticSliderCore = ElasticSliderCore;
}
<|endoftext|>


<commit_before>/*
 * grunt-imageoptim
 * https://github.com/JamieMason/grunt-imageoptim
 *
 * Copyright (c) 2013 Jamie Mason @GotNoSugarBaby
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var path = require('path');
  var pluginRoot = path.resolve(__dirname, '../');
  var localImageOptim = path.resolve(pluginRoot, 'node_modules/imageoptim-cli');

  function logMessage (message, isError) {

    // quit if message is empty or only contains whitespace
    if (!message || String(message).search(/\S/) === -1) {
      return;
    }

    // remove trailing new lines
    message = message.replace(/\n$/, '');

    if (isError) {
      grunt.fail.fatal(message);
    } else {
      grunt.log.writeln(message);
    }

  }

  grunt.registerMultiTask('imageoptim', 'Losslessly compress images from the command line', function() {

    var terminalCommand;
    var complete = 0;
    var directories = this.filesSrc;
    var done = this.async();
    var exec = require('child_process').exec;
    var options = this.options({
      imageAlpha: false,
      quitAfter: false
    });

    if (options.quitAfter && options.imageAlpha) {
      terminalCommand = './imageOptim --quit --image-alpha --directory ';
    } else if (!options.quitAfter && options.imageAlpha) {
      terminalCommand = './imageOptim --image-alpha --directory ';
    } else if (options.quitAfter && !options.imageAlpha) {
      terminalCommand = './imageOptim --quit --directory ';
    } else {
      terminalCommand = './imageOptim --directory ';
    }

    if (!directories.length) {
      grunt.fail.fatal('No valid directories were supplied for processing', 1);
    }

    directories.forEach(function(imgPath) {

      var imageOptim;

      grunt.log.writeln('Processing "' + imgPath + '"');

      imageOptim = exec(terminalCommand + imgPath, {
        cwd: localImageOptim
      }, function(error, stdout) {
        if (error !== null) {
          logMessage(stdout, true);
          done(error);
        } else {
          logMessage(stdout);
        }
      });

      imageOptim.on('exit', function(code) {
        if (++complete === directories.length) {
          done(code);
        }
      });

    });

  });

};
<commit_msg>added jpegMini option<commit_after>/*
 * grunt-imageoptim
 * https://github.com/JamieMason/grunt-imageoptim
 *
 * Copyright (c) 2013 Jamie Mason @GotNoSugarBaby
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var path = require('path');
  var pluginRoot = path.resolve(__dirname, '../');
  var localImageOptim = path.resolve(pluginRoot, 'node_modules/imageoptim-cli');

  function logMessage(message, isError) {

    // quit if message is empty or only contains whitespace
    if (!message || String(message).search(/\S/) === -1) {
      return;
    }

    // remove trailing new lines
    message = message.replace(/\n$/, '');

    if (isError) {
      grunt.fail.fatal(message);
    } else {
      grunt.log.writeln(message);
    }

  }

  grunt.registerMultiTask('imageoptim', 'Losslessly compress images from the command line', function() {

    var terminalCommand = [];
    var complete = 0;
    var directories = this.filesSrc;
    var done = this.async();
    var exec = require('child_process').exec;
    var options = this.options({
      jpegMini: false,
      imageAlpha: false,
      quitAfter: false
    });

    terminalCommand.push('./imageOptim');

    if (options.quitAfter) {
      terminalCommand.push('--quit');
    }
    if (options.imageAlpha) {
      terminalCommand.push('--image-alpha');
    }
    if (options.jpegMini) {
      terminalCommand.push('--jpeg-mini');
    }

    terminalCommand.push('--directory');

    if (!directories.length) {
      grunt.fail.fatal('No valid directories were supplied for processing', 1);
    }

    directories.forEach(function(imgPath) {

      var imageOptim;
      var execCommand = terminalCommand.concat(imgPath).join(' ');

      grunt.log.writeln('Processing "' + imgPath + '"');

      imageOptim = exec(execCommand, {
        cwd: localImageOptim
      }, function(error, stdout) {
        if (error !== null) {
          logMessage(stdout, true);
          done(error);
        } else {
          logMessage(stdout);
        }
      });

      imageOptim.on('exit', function(code) {
        if (++complete === directories.length) {
          done(code);
        }
      });

    });

  });

};
<|endoftext|>


<commit_before>'use strict';

require('./check-deps');

var chalk = require('chalk'),
    inquirer = require('inquirer');

var CommonUtil = {};

CommonUtil.header = function(grunt, msg, color) {
  msg = color ? chalk[color](msg) : msg;
  var line = Array(msg.length + 1).join('-');
  line = color ? chalk[color](line) : line;

  grunt.log.subhead(msg);
  grunt.log.writeln(line);
};

CommonUtil.createPrompt = function(input, options) {
  options = options || {};

  var prompt = {
    name: input.key,
    message: input.label,
    type: 'input'
  };

  if(input.description) {
    prompt.message += ' [' + input.description + ']';
  }

  // Required
  if(input.required) {
    prompt.message = prompt.message + '*';
    if(options.validateRequired) {
      prompt.validate = function(item) {
        return !!item;
      };
    }
  }

  // Select type
  if(input.type === 'select') {
    prompt.type = 'list';
    prompt.choices = input.input_options.map(function(choice) {
      return {
        name: choice.label,
        value: choice.value
      };
    });
    if(!input.required || !options.validateRequired) {
      prompt.choices.unshift({
        name: '(none)',
        value: ''
      });
    }
  } else if(input.type === 'datetime') {
    prompt.message += ' ⌚ ';
  } else if(input.type === 'boolean') {
    prompt.message += ' ☯ ';
  }

  prompt.message = prompt.message += ':';

  if(input.default) {
    prompt.default = input.default;
  }

  return prompt;
};

CommonUtil.promptFields = function(inputs, options, cb) {
  if(arguments.length === 2) {
    cb = options;
    options = {};
  }

  if(!('validateRequired' in options)) {
    options.validateRequired = true;
  }

  var prompts = inputs.map(function(input) {
    return CommonUtil.createPrompt(input, options);
  });

  inquirer.prompt(prompts, function(result) {
    cb(null, result);
  });
};

module.exports = CommonUtil;
<commit_msg>Fixed NPE for older runs.json files<commit_after>'use strict';

require('./check-deps');

var chalk = require('chalk'),
    inquirer = require('inquirer');

var CommonUtil = {};

CommonUtil.header = function(grunt, msg, color) {
  msg = color ? chalk[color](msg) : msg;
  var line = Array(msg.length + 1).join('-');
  line = color ? chalk[color](line) : line;

  grunt.log.subhead(msg);
  grunt.log.writeln(line);
};

CommonUtil.createPrompt = function(input, options) {
  options = options || {};

  var prompt = {
    name: input.key,
    message: input.label,
    type: 'input'
  };

  if(input.description) {
    prompt.message += ' [' + input.description + ']';
  }

  // Required
  if(input.required) {
    prompt.message = prompt.message + '*';
    if(options.validateRequired) {
      prompt.validate = function(item) {
        return !!item;
      };
    }
  }

  // Select type
  if(input.type === 'select') {
    prompt.type = 'list';
    if(input.input_options) {
      prompt.choices = input.input_options.map(function(choice) {
        return {
          name: choice.label,
          value: choice.value
        };
      });
      if(!input.required || !options.validateRequired) {
        prompt.choices.unshift({
          name: '(none)',
          value: ''
        });
      }
    }

  } else if(input.type === 'datetime') {
    prompt.message += ' ⌚ ';
  } else if(input.type === 'boolean') {
    prompt.message += ' ☯ ';
  }

  prompt.message = prompt.message += ':';

  if(input.default) {
    prompt.default = input.default;
  }

  return prompt;
};

CommonUtil.promptFields = function(inputs, options, cb) {
  if(arguments.length === 2) {
    cb = options;
    options = {};
  }

  if(!('validateRequired' in options)) {
    options.validateRequired = true;
  }

  var prompts = inputs.map(function(input) {
    return CommonUtil.createPrompt(input, options);
  });

  inquirer.prompt(prompts, function(result) {
    cb(null, result);
  });
};

module.exports = CommonUtil;
<|endoftext|>


<commit_before>var request = require('request'),
    mail = require('nodemailer').mail,
    fs = require('fs'),
    cheerio = require('cheerio'),
    builder   = require('xmlbuilder'),
    _ = require('lodash');

function Litmus(options){
  this.options = options;
  this.initVars();
}

// Initialize variables
Litmus.prototype.initVars = function() {

  this.reqObj = {
    auth: {
      user: this.options.username || '',
      pass: this.options.password || ''
    }
  };
};

// Run test
Litmus.prototype.run = function(html, title, next) {
  this.title = title;
  this.delay = this.options.delay || 3500

  if( (this.title === undefined) || (this.title.trim().length === 0) ){
    this.title = title;
  }

  this.html = html;
  this.getTests(function(body){
    var id = this.getId(body);
    this.sendTest(id);
    setTimeout(next, this.delay);
  });

};

// Grab tests from Litmus
Litmus.prototype.getTests = function(fn) {
  var self = this,
      opts = this.reqObj;
  opts.url = this.options.url + '/tests.xml';
  request.get(opts, function(err, res, body){
    if(err) { throw err; }
    fn.call(self, body);
  });
};

// Grab the name of email and set id if it matches title/subject line
Litmus.prototype.getId = function(body) {
  var $ = cheerio.load(body, {xmlMode: true}),
      $allNameTags = $('name'),
      subjLine = this.title,
      id,
      $matchedName = $allNameTags.filter(function(){
        return $(this).text() === subjLine;
      });

  if($matchedName.length){
    id = $matchedName.parent().children('id').text();
  }

  return id;
};

// Send a new version if id is availabe otherwise send a new test
Litmus.prototype.sendTest = function(id) {
  var self = this;
  var opts = _.clone(this.reqObj);

  opts.headers = { 'Content-type': 'application/xml', 'Accept': 'application/xml' };
  opts.body = this.getBuiltXml(this.html, this.title);

  if(id){
    this.log('Sending new version: '.bold + this.title);
    opts.url = this.options.url + '/tests/'+ id + '/versions.xml';
    request.post(opts, this.mailNewVersion.bind(this));
  }else{
    this.log('Sending new test: '.bold + this.title);
    opts.url = this.options.url + '/emails.xml';
    request.post(opts, this.logHeaders.bind(this));
  }
};

// Logs headers of response once email is sent
Litmus.prototype.logHeaders = function(err, res, body) {
  if(err){ throw err; }

  var headers = res.headers;

  Object.keys(headers).forEach(function(key){
    console.log(key.toUpperCase().bold + ': ' + headers[key]);
  });

  console.log('---------------------\n' + body);
  this.logSuccess('Test sent!');
};

// Mail a new test using test email Litmus provides
Litmus.prototype.mailNewVersion = function(err, res, body) {
  if(err){ throw err; }

  var $ = cheerio.load(body),
      guid = $('url_or_guid').text();

  mail({
      from: 'no-reply@test.com',
      to: guid,
      subject: this.title,
      text: '',
      html: this.html
  });
  this.logSuccess('New version sent!');

};

Litmus.prototype.getBuiltXml = function(html, title) {
  var xmlApplications = builder.create('applications').att('type', 'array');

  _.each(this.options.clients, function(app) {
    var item = xmlApplications.ele('application');

    item.ele('code', app);
  });

  //Build Xml to send off, Join with Application XMl
  var xml = builder.create('test_set')
    .importXMLBuilder(xmlApplications)
    .ele('save_defaults', 'false').up()
    .ele('use_defaults', 'false').up()
    .ele('email_source')
      .ele('body').dat(html).up()
      .ele('subject', title)
    .end({pretty: true});

  return xml;
};

// Logging helpers
Litmus.prototype.log = function(str) {
  return console.log(str.cyan);
};

Litmus.prototype.logSuccess = function(str) {
  return console.log(str.green);
};

module.exports = Litmus;
<commit_msg>added logging table<commit_after>var request = require('request'),
    mail = require('nodemailer').mail,
    fs = require('fs'),
    cheerio = require('cheerio'),
    builder   = require('xmlbuilder'),
    Table = require('cli-table'),
    _ = require('lodash');

function Litmus(options){
  this.options = options;
  this.initVars();
}

// Initialize variables
Litmus.prototype.initVars = function() {

  this.reqObj = {
    auth: {
      user: this.options.username || '',
      pass: this.options.password || ''
    }
  };
};

// Run test
Litmus.prototype.run = function(html, title, next) {
  this.title = title;
  this.delay = this.options.delay || 3500;

  if( (this.title === undefined) || (this.title.trim().length === 0) ){
    this.title = title;
  }

  this.html = html;
  this.getTests(function(body){
    var id = this.getId(body);
    this.sendTest(id);
    setTimeout(next, this.delay);
  });

};

// Grab tests from Litmus
Litmus.prototype.getTests = function(fn) {
  var self = this,
      opts = this.reqObj;
  opts.url = this.options.url + '/tests.xml';
  request.get(opts, function(err, res, body){
    if(err) { throw err; }
    fn.call(self, body);
  });
};

// Grab the name of email and set id if it matches title/subject line
Litmus.prototype.getId = function(body) {
  var $ = cheerio.load(body, {xmlMode: true}),
      $allNameTags = $('name'),
      subjLine = this.title,
      id,
      $matchedName = $allNameTags.filter(function(){
        return $(this).text() === subjLine;
      });

  if($matchedName.length){
    id = $matchedName.parent().children('id').text();
  }

  return id;
};

// Calculate and get the average time for test to complete
Litmus.prototype.getAvgTime = function(body) {
  var $ = cheerio.load(body, { xmlMode: true });
  var avgTimes = $('average_time_to_process');
  var count = 0;
  avgTimes.each(function(i, el){
    count += +$(this).text();
  });

  if(count < 60){
    return count + ' secs';
  }else{
    return  Math.round((count/avgTimes.length)/60) + ' mins';
  }
};

// Log status of test
Litmus.prototype.getStatus = function(body) {
  var $ = cheerio.load(body, { xmlMode: true }),
      statuses = $('status'),
      delayed = [],
      unavailable = [],
      statusCode,
      application;

  statuses.each(function(i, el){

    var $this = $(this);
    statusCode = +$this.text();
    application = $this.parent().children('application_long_name').text();

    if(statusCode === 1){ delayed.push(application); }

    if(statusCode === 2){ unavailable.push(application); }

  });

  return {
    delayed: delayed.join('\n'),
    unavailable: unavailable.join('\n')
  };

};

Litmus.prototype.logStatusTable = function(body) {
  var table = new Table(),
      delayed = this.getStatus(body).delayed,
      unavailable = this.getStatus(body).unavailable,
      avgTime = this.getAvgTime(body),
      values = [];

  table.options.head = ['Avg. Time to Complete'.bold];
  values.push(avgTime);

  if(delayed.length > 0){
    table.options.head.push('Delayed'.bold);
    values.push(delayed);
  }

  if(unavailable.length > 0){
    table.options.head.push('Unavailable'.bold);
    values.push(unavailable);
  }

  table.push(values);

  console.log(table.toString());
};

// Send a new version if id is availabe otherwise send a new test
Litmus.prototype.sendTest = function(id) {
  var self = this;
  var opts = _.clone(this.reqObj);

  opts.headers = { 'Content-type': 'application/xml', 'Accept': 'application/xml' };
  opts.body = this.getBuiltXml(this.html, this.title);

  if(id){
    this.log('Sending new version: '.bold + this.title);
    opts.url = this.options.url + '/tests/'+ id + '/versions.xml';
    request.post(opts, this.mailNewVersion.bind(this));
  }else{
    this.log('Sending new test: '.bold + this.title);
    opts.url = this.options.url + '/emails.xml';
    request.post(opts, this.logHeaders.bind(this));
  }
};

// Logs headers of response once email is sent
Litmus.prototype.logHeaders = function(err, res, body) {
  if(err){ throw err; }

  var headers = res.headers;
  var status = parseFloat(headers.status, 10);

  Object.keys(headers).forEach(function(key){
    console.log(key.toUpperCase().bold + ': ' + headers[key]);
  });

  console.log('---------------------\n' + body);

  if(status > 199 && status < 300){
    this.logSuccess('Test sent!');
    this.logStatusTable(body);
  } else {
    throw new Error(headers.status);
  }

};

// Mail a new test using test email Litmus provides
Litmus.prototype.mailNewVersion = function(err, res, body) {
  if(err){ throw err; }

  var $ = cheerio.load(body),
      guid = $('url_or_guid').text();

  mail({
      from: 'no-reply@test.com',
      to: guid,
      subject: this.title,
      text: '',
      html: this.html
  });
  this.logSuccess('New version sent!');
  this.logStatusTable(body);

};

Litmus.prototype.getBuiltXml = function(html, title) {
  var xmlApplications = builder.create('applications').att('type', 'array');

  _.each(this.options.clients, function(app) {
    var item = xmlApplications.ele('application');

    item.ele('code', app);
  });

  //Build Xml to send off, Join with Application XMl
  var xml = builder.create('test_set')
    .importXMLBuilder(xmlApplications)
    .ele('save_defaults', 'false').up()
    .ele('use_defaults', 'false').up()
    .ele('email_source')
      .ele('body').dat(html).up()
      .ele('subject', title)
    .end({pretty: true});

  return xml;
};

// Logging helpers
Litmus.prototype.log = function(str) {
  return console.log(str.cyan);
};

Litmus.prototype.logSuccess = function(str) {
  return console.log(str.green);
};

module.exports = Litmus;
<|endoftext|>