<commit_before>chorus.dialogs.ExistingTableImportCSV = chorus.dialogs.Base.extend({
    className: "existing_table_import_csv",
    additionalClass: "table_import_csv",
    title: t("dataset.import.table.title"),
    delimiter: ',',

    events: {
        "click button.submit": "startImport",
        "change #include_header": "refreshCSV",
        "keyup input.delimiter[name=custom_delimiter]": "setOtherDelimiter",
        "paste input.delimiter[name=custom_delimiter]": "setOtherDelimiter",
        "click input.delimiter[type=radio]": "setDelimiter",
        "click input#delimiter_other": "focusOtherInputField"
    },

    setup: function() {
        this.resource = this.csv = this.options.csv;
        this.tableName = this.csv.get("toTable");
        chorus.PageEvents.subscribe("choice:setType", this.onSelectType, this);
        this.dataset = new chorus.models.Dataset({ workspace: {id: this.csv.get("workspaceId")}, id: this.options.datasetId })
        this.requiredResources.add(this.dataset);
        this.dataset.fetch();
        this.destinations = [];
    },

    render: function() {
        var api = this.$(".tbody").data("jsp");
        this.scrollPosX = api ? api.getContentPositionX() : 0;
        this.scrollPosY = api ? api.getContentPositionY() : 0;

        this._super("render", arguments);
    },

    postRender: function() {

        var $tbody = this.$(".tbody");
        $tbody.unbind("scroll.follow_header");
        $tbody.bind("scroll.follow_header", _.bind(this.adjustHeaderPosition, this));
        $tbody.scrollTop(this.scrollPosY)
        $tbody.scrollLeft(this.scrollPosX);
        this.$(".thead").css({ "left": -this.scrollPosX });

        this.setupScrolling(this.$(".tbody"));

        this.cleanUpQtip();

        var self = this;
        _.each(this.$(".column_mapping .map"), function(map, i) {
            var content = $("

");
            _.each(self.dataset.get("columnNames"), function(column) {
                var check_wrapper = $('

')
                if (column.name === self.destinations[i].name) {
                    check_wrapper.find(".check").removeClass("hidden");
                }
                var frequency = (_.filter(self.destinations, function(destination) {
                    return destination.name === column.name
                })).length;
                var frequency_text = "";

                var selected_status = "unselected";
                if (frequency) {
                    selected_status = frequency > 1 ? "selection_conflict" : "selected";
                    frequency_text = " (" + frequency + ")";
                }

                content.append($("
").append($(check_wrapper.outerHtml() +
                    '' + column.name + frequency_text + '' +
                    '' + chorus.models.DatabaseColumn.humanTypeMap[column.typeCategory] + '')));
            });

            chorus.menu($(map), {
                content: content,
                classes: "table_import_csv",
                contentEvents: {
                    'a.name': _.bind(self.destinationColumnSelected, self)
                }
            });
        })

        self.$("input.delimiter").removeAttr("checked");
        if (_.contains([",", "\t", ";", " "], self.delimiter)) {
            self.$("input.delimiter[value='" + self.delimiter + "']").attr("checked", "true");
        } else {
            self.$("input#delimiter_other").attr("checked", "true");
        }
    },

    destinationColumnSelected: function(e, api) {
        e.preventDefault();
        var qtip_launch_link = api.elements.target.find("a");
        qtip_launch_link.text($(e.target).attr("title"));
        this.destinations = _.map(this.$(".column_mapping .map a"), function(link) {
            return {name: $(link).text()};
        });

        var self = this;

        this.destinations = _.map(this.destinations, function(destination) {
            var frequency = (_.filter(self.destinations, function(destination_loop) {
                return (destination.name != t("dataset.import.table.existing.select_one")) &&
                    ( destination.name === destination_loop.name)
            })).length;
            return _.extend(destination, {frequency: frequency});
        })

        this.render();
    },

    additionalContext: function() {
        var self = this;

        var columns = this.csv.columnOrientedData();
        columns = _.map(columns, function(column, index) {
            if (index >= self.destinations.length) {
                self.destinations.push({name: t("dataset.import.table.existing.select_one"), frequency: 0});
            }
            var selected_status = (self.destinations[index].frequency == 1) ? "selected" : "selection_conflict";

            return _.extend(column, {destination: self.destinations[index].name, selected_status: selected_status});
        });

        return {
            columns: columns,
            delimiter: this.other_delimiter ? this.delimiter : '',
            directions: t("dataset.import.table.existing.directions", {
                toTable: this.csv.get("toTable")
            }),
            count: (_.filter(self.destinations, function(destination) {
                    return destination.name != t("dataset.import.table.existing.select_one")
                }
            )).length,
            total: columns.length
        }
    },

    startImport: function() {
        this.$('button.submit').startLoading("dataset.import.importing");
        var $names = this.$(".column_names input:text");
        var $types = this.$(".data_types .chosen");

        var columnData = _.map($names, function(name, i) {
            return {
                columnName: chorus.Mixins.dbHelpers.safePGName($(name).val()),
                columnType: $types.eq(i).text(),
                columnOrder: i + 1
            }
        })
        this.csv.set({
            delimiter: this.delimiter,
            columnsDef: JSON.stringify(columnData)
        })

        this.csv.bindOnce("saved", function() {
            this.closeModal();
            chorus.toast("dataset.import.started");
            chorus.PageEvents.broadcast("csv_import:started");
        }, this);

        this.csv.bindOnce("saveFailed", function() {
            this.$("button.submit").stopLoading();
        }, this);

        this.$("button.submit").startLoading("dataset.import.importing");

        this.csv.save();
    },

    refreshCSV: function() {
        this.csv.set({include_header: !!(this.$("#include_header").attr("checked")), delimiter: this.delimiter});
        this.render();
        this.recalculateScrolling();
    },

    adjustHeaderPosition: function() {
        this.$(".thead").css({ "left": -this.scrollLeft() });
    },

    scrollLeft: function() {
        var api = this.$(".tbody").data("jsp");
        return api && api.getContentPositionX();
    },

    setDelimiter: function(e) {
        if (e.target.value == "other") {
            this.delimiter = this.$("input[name=custom_delimiter]").val();
            this.other_delimiter = true;
        } else {
            this.delimiter = e.target.value;
            this.other_delimiter = false;
        }
        this.refreshCSV();
    },

    focusOtherInputField: function(e) {
        this.$("input[name=custom_delimiter]").focus();
    },

    setOtherDelimiter: function() {
        this.$("input.delimiter[type=radio]").removeAttr("checked");
        var otherRadio = this.$("input#delimiter_other");
        otherRadio.attr("checked", true)
        otherRadio.click();
    },

    cleanUpQtip: function() {
        this.$(".column_mapping .map").qtip("destroy");
        this.$(".column_mapping .map").removeData("qtip");
    }
});<commit_msg>Map columns from CSV file into existing table WIP minor refactoring [#25379253]<commit_after>chorus.dialogs.ExistingTableImportCSV = chorus.dialogs.Base.extend({
    className: "existing_table_import_csv",
    additionalClass: "table_import_csv",
    title: t("dataset.import.table.title"),
    delimiter: ',',

    events: {
        "click button.submit": "startImport",
        "change #include_header": "refreshCSV",
        "keyup input.delimiter[name=custom_delimiter]": "setOtherDelimiter",
        "paste input.delimiter[name=custom_delimiter]": "setOtherDelimiter",
        "click input.delimiter[type=radio]": "setDelimiter",
        "click input#delimiter_other": "focusOtherInputField"
    },

    setup: function() {
        this.resource = this.csv = this.options.csv;
        this.tableName = this.csv.get("toTable");
        chorus.PageEvents.subscribe("choice:setType", this.onSelectType, this);
        this.dataset = new chorus.models.Dataset({ workspace: {id: this.csv.get("workspaceId")}, id: this.options.datasetId })
        this.requiredResources.add(this.dataset);
        this.dataset.fetch();
        this.destinations = [];
    },

    render: function() {
        var api = this.$(".tbody").data("jsp");
        this.scrollPosX = api ? api.getContentPositionX() : 0;
        this.scrollPosY = api ? api.getContentPositionY() : 0;

        this._super("render", arguments);
    },

    postRender: function() {

        this.handleScrolling();

        this.cleanUpQtip();

        var self = this;
        _.each(this.$(".column_mapping .map"), function(map, i) {
            var content = $("

");
            _.each(self.dataset.get("columnNames"), function(column) {
                content.append(_.bind(self.createQtipContent, self, column, self.destinations[i].name));
            });

            chorus.menu($(map), {
                content: content,
                classes: "table_import_csv",
                contentEvents: {
                    'a.name': _.bind(self.destinationColumnSelected, self)
                }
            });
        })

        self.$("input.delimiter").removeAttr("checked");
        if (_.contains([",", "\t", ";", " "], self.delimiter)) {
            self.$("input.delimiter[value='" + self.delimiter + "']").attr("checked", "true");
        } else {
            self.$("input#delimiter_other").attr("checked", "true");
        }
    },

    destinationColumnSelected: function(e, api) {
        e.preventDefault();
        var qtip_launch_link = api.elements.target.find("a");
        qtip_launch_link.text($(e.target).attr("title"));
        this.destinations = _.map(this.$(".column_mapping .map a"), function(link) {
            return {name: $(link).text()};
        });

        var self = this;

        this.destinations = _.map(this.destinations, function(destination) {
            var frequency = (_.filter(self.destinations, function(destination_loop) {
                return (destination.name != t("dataset.import.table.existing.select_one")) &&
                    ( destination.name === destination_loop.name)
            })).length;
            return _.extend(destination, {frequency: frequency});
        })

        this.render();
    },

    additionalContext: function() {
        var self = this;

        var columns = this.csv.columnOrientedData();
        columns = _.map(columns, function(column, index) {
            if (index >= self.destinations.length) {
                self.destinations.push({name: t("dataset.import.table.existing.select_one"), frequency: 0});
            }
            var selected_status = (self.destinations[index].frequency == 1) ? "selected" : "selection_conflict";

            return _.extend(column, {destination: self.destinations[index].name, selected_status: selected_status});
        });

        return {
            columns: columns,
            delimiter: this.other_delimiter ? this.delimiter : '',
            directions: t("dataset.import.table.existing.directions", {
                toTable: this.csv.get("toTable")
            }),
            count: (_.filter(self.destinations, function(destination) {
                    return destination.name != t("dataset.import.table.existing.select_one")
                }
            )).length,
            total: columns.length
        }
    },

    startImport: function() {
        this.$('button.submit').startLoading("dataset.import.importing");
        var $names = this.$(".column_names input:text");
        var $types = this.$(".data_types .chosen");

        var columnData = _.map($names, function(name, i) {
            return {
                columnName: chorus.Mixins.dbHelpers.safePGName($(name).val()),
                columnType: $types.eq(i).text(),
                columnOrder: i + 1
            }
        })
        this.csv.set({
            delimiter: this.delimiter,
            columnsDef: JSON.stringify(columnData)
        })

        this.csv.bindOnce("saved", function() {
            this.closeModal();
            chorus.toast("dataset.import.started");
            chorus.PageEvents.broadcast("csv_import:started");
        }, this);

        this.csv.bindOnce("saveFailed", function() {
            this.$("button.submit").stopLoading();
        }, this);

        this.$("button.submit").startLoading("dataset.import.importing");

        this.csv.save();
    },

    refreshCSV: function() {
        this.csv.set({include_header: !!(this.$("#include_header").attr("checked")), delimiter: this.delimiter});
        this.render();
        this.recalculateScrolling();
    },

    adjustHeaderPosition: function() {
        this.$(".thead").css({ "left": -this.scrollLeft() });
    },

    scrollLeft: function() {
        var api = this.$(".tbody").data("jsp");
        return api && api.getContentPositionX();
    },

    setDelimiter: function(e) {
        if (e.target.value == "other") {
            this.delimiter = this.$("input[name=custom_delimiter]").val();
            this.other_delimiter = true;
        } else {
            this.delimiter = e.target.value;
            this.other_delimiter = false;
        }
        this.refreshCSV();
    },

    focusOtherInputField: function(e) {
        this.$("input[name=custom_delimiter]").focus();
    },

    setOtherDelimiter: function() {
        this.$("input.delimiter[type=radio]").removeAttr("checked");
        var otherRadio = this.$("input#delimiter_other");
        otherRadio.attr("checked", true)
        otherRadio.click();
    },

    cleanUpQtip: function() {
        this.$(".column_mapping .map").qtip("destroy");
        this.$(".column_mapping .map").removeData("qtip");
    },

    handleScrolling: function() {
        var $tbody = this.$(".tbody");
        $tbody.unbind("scroll.follow_header");
        $tbody.bind("scroll.follow_header", _.bind(this.adjustHeaderPosition, this));
        $tbody.scrollTop(this.scrollPosY)
        $tbody.scrollLeft(this.scrollPosX);
        this.$(".thead").css({ "left": -this.scrollPosX });

        this.setupScrolling(this.$(".tbody"));
    },

    createQtipContent: function(column, selectedName) {
        var check_wrapper = $('

')

        if (column.name === selectedName) {
            check_wrapper.find(".check").removeClass("hidden");
        }
        var frequency = (_.filter(this.destinations, function(destination) {
            return destination.name === column.name
        })).length;
        var frequency_text = "";

        var selected_status = "unselected";
        if (frequency) {
            selected_status = frequency > 1 ? "selection_conflict" : "selected";
            frequency_text = " (" + frequency + ")";
        }

        return ($("
").append($(check_wrapper.outerHtml() +
            '' + column.name + frequency_text + '' +
            '' + chorus.models.DatabaseColumn.humanTypeMap[column.typeCategory] + '')));
    }
});<|endoftext|>


<commit_before>// Category View
// =============

// Includes file dependencies
define([
    "underscore",
	"jquery",
	"backbone",
    "text!../templates/character-summarize-list-item.html",
    "marionette"
], function( _, $, Backbone, character_summarize_list_item_html, Marionette ) {

    var SummaryView = Marionette.ItemView.extend({
        tagName: "li",
        className: "ul-li-has-thumb",
        template: _.template(character_summarize_list_item_html),
        initialize: function(options) {
            this.mode = options.mode
        },

        templateHelpers: function () {
            var self = this;
            return {
                e: self.model,
                mode: self.mode,
            };
        },

        events: {
            "click": "clicked",
        },

        clicked: function(e) {
            var self = this;
            e.preventDefault();
            $.mobile.loading("show");
            var targete = $(e.currentTarget);
            var pickedId = targete.attr("backendId");
            var tmpl = _.template(self.click_url)({character_id: pickedId});
            window.location.hash = tmpl;
        },

        onRender: function () {
            this.$el.enhanceWithin();
        }
    });

    var CharactersView = Marionette.CollectionView.extend( {
        tagName: 'ul',
        childView: SummaryView,
        childViewOptions: function(model, index) {
            var self = this;
            return {
                mode: self.mode
            }
        },

        onRender: function () {
            var self = this;
            /*
            self.$el.filterable({
                input: "#troupes-characters-filter"
            });
            self.$el.filterable("enable");
            */
            self.$el.attr('data-role', 'listview');
            self.$el.attr('data-inset', 'true');
            self.$el.attr('data-filter', 'true');
            self.$el.attr('data-input', '#troupes-summarize-characters-filter');
            /*
            self.$el.enhanceWithin();
            */
        },

        /*
        onAddChild: function() {
            var self = this;
            self.$el.filterable("refresh");
        },
        */
        onDomRefresh: function() {
            var self = this;
            /*
            self.$el.filterable("refresh");
            self.$el.filterable("enable");
            */
        }

    } );

    var ButtonView = Marionette.ItemView.extend({
        template: _.template("<button>Click me for doom</button>"),
        triggers: {
            "click": "doom:clicked"
        },
    })

    var View = Marionette.LayoutView.extend({
        el: "#troupe-summarize-characters-all > div[data-role='main']",
        regions: {
            sections: "#sections",
            list: "#troupe-summarize-characters-list"
        },
        childEvents: {
            "doom:clicked": "doomclicked",
        },
        doomclicked: function () {
            var self = this;
            console.log("Doom was definitely clicked");
            self.list.currentView.mode = "backgrounds";
            self.collection.reset(_.map(self.collection.models));
            /*
            var options = self.options || {};
            self.showChildView(
                'list',
                new BackgroundsView({
                    collection: self.collection}),
                options);
            self.$el.enhanceWithin();
            */
        },
        setup: function() {
            var self = this;
            var options = self.options || {};
            self.showChildView(
                'sections',
                new ButtonView(),
                options);
            self.showChildView(
                'list',
                new CharactersView({
                    collection: self.collection}),
                options);
            this.$el.enhanceWithin();
            return self;
        }
    });

    // Returns the View class
    return View;

} );<commit_msg>Show categories on click<commit_after>// Category View
// =============

// Includes file dependencies
define([
    "underscore",
	"jquery",
	"backbone",
    "text!../templates/character-summarize-list-item.html",
    "marionette",
    "../models/Vampire"
], function( _, $, Backbone, character_summarize_list_item_html, Marionette, Vampire ) {

    var SummaryView = Marionette.ItemView.extend({
        tagName: "li",
        className: "ul-li-has-thumb",
        template: _.template(character_summarize_list_item_html),
        initialize: function(options) {
            this.mode = options.mode
        },

        templateHelpers: function () {
            var self = this;
            return {
                e: self.model,
                mode: self.mode,
            };
        },

        events: {
            "click": "clicked",
        },

        clicked: function(e) {
            var self = this;
            e.preventDefault();
            $.mobile.loading("show");
            var targete = $(e.currentTarget);
            var pickedId = targete.attr("backendId");
            var tmpl = _.template(self.click_url)({character_id: pickedId});
            window.location.hash = tmpl;
        },

        onRender: function () {
            this.$el.enhanceWithin();
        }
    });

    var CharactersView = Marionette.CollectionView.extend( {
        tagName: 'ul',
        childView: SummaryView,
        childViewOptions: function(model, index) {
            var self = this;
            return {
                mode: self.mode
            }
        },

        onRender: function () {
            var self = this;
            /*
            self.$el.filterable({
                input: "#troupes-characters-filter"
            });
            self.$el.filterable("enable");
            */
            self.$el.attr('data-role', 'listview');
            self.$el.attr('data-inset', 'true');
            self.$el.attr('data-filter', 'true');
            self.$el.attr('data-input', '#troupes-summarize-characters-filter');
            /*
            self.$el.enhanceWithin();
            */
        },

        /*
        onAddChild: function() {
            var self = this;
            self.$el.filterable("refresh");
        },
        */
        onDomRefresh: function() {
            var self = this;
            /*
            self.$el.filterable("refresh");
            self.$el.filterable("enable");
            */
        }

    } );

    var FilterButton = Marionette.ItemView.extend({
        template: function (serialized_model) {
            return _.template("<button><%= name %></button>")(serialized_model);
        },
        events: {
            "click": "filterwith"
        },
        filterwith: function () {
            this.triggerMethod("filterwith", this.model.get("mode"));
        }
    });

    var FiltersView = Marionette.CollectionView.extend({
        childView: FilterButton
    })

    var Mode = Backbone.Model.extend({

    });

    var Modes = Backbone.Collection.extend({
        model: Mode
    })

    var View = Marionette.LayoutView.extend({
        el: "#troupe-summarize-characters-all > div[data-role='main']",
        regions: {
            sections: "#sections",
            list: "#troupe-summarize-characters-list"
        },
        childEvents: {
            "filterwith": "filterwith",
        },
        filterwith: function (childView, mode) {
            var self = this;
            self.list.currentView.mode = mode;
            self.collection.reset(_.map(self.collection.models));
            /*
            var options = self.options || {};
            self.showChildView(
                'list',
                new BackgroundsView({
                    collection: self.collection}),
                options);
            self.$el.enhanceWithin();
            */
        },
        setup: function() {
            var self = this;
            var options = self.options || {};
            var modes = new Modes;
            self.showChildView(
                'sections',
                new FiltersView({
                    collection: modes
                }),
                options);
            _.each(Vampire.all_simpletrait_categories(), function (e) {
                modes.add(new Mode({mode: e[0], name: e[1], category: e[2]}));
            });
            self.showChildView(
                'list',
                new CharactersView({
                    collection: self.collection}),
                options);
            this.$el.enhanceWithin();
            return self;
        }
    });

    // Returns the View class
    return View;

} );<|endoftext|>


<commit_before>
exports.commands = {
	/*stafflist: 'authlist',
	authlist: function (target, room, user, connection) {
		var rankLists = {};
		for (var u in Users.usergroups) {
			var rank = Users.usergroups[u][0];
			var name = Users.usergroups[u].slice(1);
			if (!rankLists[rank]) rankLists[rank] = [];
			if (name) name = name.replace("\n", "").replace("\r", "");
			rankLists[rank].push(name);
		}
		var buffer = [];
		Object.keys(rankLists).sort(function (a, b) {
			return Config.groups[b].rank - Config.groups[a].rank;
		}).forEach(function (r) {
			buffer.push(Config.groups[r].name + "s (" + r + "):\n" + rankLists[r].sort().join(", "));
		});

		if (!buffer.length) {
			buffer = "This server has no auth.";
			return connection.popup("This server has no auth.");
		}
		connection.popup(buffer.join("\n\n"));
	},*/

	plugdj: function () {
		if (!this.canBroadcast()) return;
		this.sendReplyBox('

Radio del Servidor Dropp');");

	},

	postimage: 'image',
	image: function (target, room, user) {
		if (!target) return this.sendReply('Usage: /image link, size');
		if (!this.can('ban', room)) return false;
		if (!this.canBroadcast()) return;

		var targets = target.split(',');
		if (targets.length !== 2) {
			return this.sendReply('|raw|');
		}
		if (parseInt(targets[1]) <= 0 || parseInt(targets[1]) > 100) return this.parse('Usage: /image link, size (1-100)');
		this.sendReply('|raw|');
	},

	cssedit: function (target, room, user, connection) {
		if (!user.hasConsoleAccess(connection)) {return this.sendReply("/cssedit - Access denied.");}
		var fsscript = require('fs');
		if (!target) {
			if (!fsscript.existsSync(DATA_DIR + "custom.css")) return this.sendReply("custom.css no existe.");
			return this.sendReplyBox(fsscript.readFileSync(DATA_DIR + "custom.css").toString());
		}
		fsscript.writeFileSync(DATA_DIR + "custom.css", target.toString());
		this.sendReply("custom.css editado correctamente.");
	},

	destroymodlog: function (target, room, user, connection) {
		if (!user.hasConsoleAccess(connection)) {return this.sendReply("/destroymodlog - Access denied.");}
		var fsscript = require('fs');
		var logPath = LOGS_DIR + 'modlog/';
		if (CommandParser.modlog && CommandParser.modlog[room.id])  {
			CommandParser.modlog[room.id].close();
			delete CommandParser.modlog[room.id];
		}
		try {
			fsscript.unlinkSync(logPath + "modlog_" + room.id + ".txt");
			this.addModCommand(user.name + " ha destruido el modlog de esta sala." + (target ? ('(' + target + ')') : ''));
		} catch (e) {
			this.sendReply("No se puede destruir el modlog de esta sala.");
		}
	},

	clearall: function (target, room, user, connection) {
		if (!this.can('clearall')) return;
		var len = room.log.length,
			users = [];
		while (len--) {
			room.log[len] = '';
		}
		for (var user in room.users) {
			users.push(user);
			Users.get(user).leaveRoom(room, Users.get(user).connections[0]);
		}
		len = users.length;
		setTimeout(function() {
			while (len--) {
				Users.get(users[len]).joinRoom(room, Users.get(users[len]).connections[0]);
			}
		}, 1000);
	},

	roomlist: function (target, room, user) {
		if (!this.can('roomlist')) return;
		var rooms = Object.keys(Rooms.rooms);
		var len = rooms.length;
		var official = ['Salas oficiales:

'];
		var nonOfficial = ['
Salas no-oficiales:

'];
		var privateRoom = ['
Salas privadas:

'];
		while (len--) {
			var _room = Rooms.rooms[rooms[(rooms.length - len) - 1]];
			if (_room.type === 'chat') {
				if (_room.isOfficial) {
					official.push(('' + _room.title + ' |'));
				} else if (_room.isPrivate) {
					privateRoom.push(('' + _room.title + ' |'));
				} else {
					nonOfficial.push(('' + _room.title + ' |'));
				}
			}
		}
		this.sendReplyBox(official.join(' ') + nonOfficial.join(' ') + privateRoom.join(' '));
	}
};
<commit_msg>Update custom.js<commit_after>
exports.commands = {
	/*stafflist: 'authlist',
	authlist: function (target, room, user, connection) {
		var rankLists = {};
		for (var u in Users.usergroups) {
			var rank = Users.usergroups[u][0];
			var name = Users.usergroups[u].slice(1);
			if (!rankLists[rank]) rankLists[rank] = [];
			if (name) name = name.replace("\n", "").replace("\r", "");
			rankLists[rank].push(name);
		}
		var buffer = [];
		Object.keys(rankLists).sort(function (a, b) {
			return Config.groups[b].rank - Config.groups[a].rank;
		}).forEach(function (r) {
			buffer.push(Config.groups[r].name + "s (" + r + "):\n" + rankLists[r].sort().join(", "));
		});

		if (!buffer.length) {
			buffer = "This server has no auth.";
			return connection.popup("This server has no auth.");
		}
		connection.popup(buffer.join("\n\n"));
	},*/

	plugdj: function () {
		if (!this.canBroadcast()) return;
		this.sendReplyBox('

Plug-DJ del Servidor Dropp');

	},

	postimage: 'image',
	image: function (target, room, user) {
		if (!target) return this.sendReply('Usage: /image link, size');
		if (!this.can('ban', room)) return false;
		if (!this.canBroadcast()) return;

		var targets = target.split(',');
		if (targets.length !== 2) {
			return this.sendReply('|raw|');
		}
		if (parseInt(targets[1]) <= 0 || parseInt(targets[1]) > 100) return this.parse('Usage: /image link, size (1-100)');
		this.sendReply('|raw|');
	},

	cssedit: function (target, room, user, connection) {
		if (!user.hasConsoleAccess(connection)) {return this.sendReply("/cssedit - Access denied.");}
		var fsscript = require('fs');
		if (!target) {
			if (!fsscript.existsSync(DATA_DIR + "custom.css")) return this.sendReply("custom.css no existe.");
			return this.sendReplyBox(fsscript.readFileSync(DATA_DIR + "custom.css").toString());
		}
		fsscript.writeFileSync(DATA_DIR + "custom.css", target.toString());
		this.sendReply("custom.css editado correctamente.");
	},

	destroymodlog: function (target, room, user, connection) {
		if (!user.hasConsoleAccess(connection)) {return this.sendReply("/destroymodlog - Access denied.");}
		var fsscript = require('fs');
		var logPath = LOGS_DIR + 'modlog/';
		if (CommandParser.modlog && CommandParser.modlog[room.id])  {
			CommandParser.modlog[room.id].close();
			delete CommandParser.modlog[room.id];
		}
		try {
			fsscript.unlinkSync(logPath + "modlog_" + room.id + ".txt");
			this.addModCommand(user.name + " ha destruido el modlog de esta sala." + (target ? ('(' + target + ')') : ''));
		} catch (e) {
			this.sendReply("No se puede destruir el modlog de esta sala.");
		}
	},

	clearall: function (target, room, user, connection) {
		if (!this.can('clearall')) return;
		var len = room.log.length,
			users = [];
		while (len--) {
			room.log[len] = '';
		}
		for (var user in room.users) {
			users.push(user);
			Users.get(user).leaveRoom(room, Users.get(user).connections[0]);
		}
		len = users.length;
		setTimeout(function() {
			while (len--) {
				Users.get(users[len]).joinRoom(room, Users.get(users[len]).connections[0]);
			}
		}, 1000);
	},

	roomlist: function (target, room, user) {
		if (!this.can('roomlist')) return;
		var rooms = Object.keys(Rooms.rooms);
		var len = rooms.length;
		var official = ['Salas oficiales:

'];
		var nonOfficial = ['
Salas no-oficiales:

'];
		var privateRoom = ['
Salas privadas:

'];
		while (len--) {
			var _room = Rooms.rooms[rooms[(rooms.length - len) - 1]];
			if (_room.type === 'chat') {
				if (_room.isOfficial) {
					official.push(('' + _room.title + ' |'));
				} else if (_room.isPrivate) {
					privateRoom.push(('' + _room.title + ' |'));
				} else {
					nonOfficial.push(('' + _room.title + ' |'));
				}
			}
		}
		this.sendReplyBox(official.join(' ') + nonOfficial.join(' ') + privateRoom.join(' '));
	}
};
<|endoftext|>


<commit_before>console.time("imCMS JS loaded");

Function.prototype.bindArgs = function () {
    return this.bind.apply(this, [null].concat(Array.prototype.slice.call(arguments)));
};
Function.prototype.applyAsync = function (args, context) {
    setTimeout(this.apply.bind(this, context, args));
};
if (!String.prototype.endsWith) {
    Object.defineProperty(String.prototype, 'endsWith', {
        value: function (searchString, position) {
            var subjectString = this.toString();
            if (position === undefined || position > subjectString.length) {
                position = subjectString.length;
            }
            position -= searchString.length;
            var lastIndex = subjectString.indexOf(searchString, position);
            return lastIndex !== -1 && lastIndex === position;
        }
    });
}
if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: function (searchString, position) {
            position = position || 0;
            return this.indexOf(searchString, position) === position;
        }
    });
}
(function (imcms) {
    function registerModule(id, module) {
        if (imcms.modules[id]) {
            console.error("Module already registered! " + id);
            return;
        }

        if (imcms.config.dependencies[id] && imcms.config.dependencies[id].onLoad) {
            module = imcms.config.dependencies[id].onLoad(module);
        }

        imcms.modules[id] = module;
        console.log("%c Module " + id + " registered.", "color: blue;");
        runModuleLoader.applyAsync();
    }

    function getModule(id) {
        return imcms.modules[id];
    }

    function getDependency(id) {
        return imcms.config.dependencies[id];
    }

    function loadScript(dependency, id) {
        var registerFunction;
        dependency.id = id;

        if (dependency.moduleName) {
            registerFunction = registerModule.bindArgs(dependency.moduleName, true);
        }

        var onLoad = function () {
            registerFunction && registerFunction.applyAsync();
            runModuleLoader.applyAsync();
        };

        getScript(dependency, onLoad);
    }

    function loadDependencyById(id) {
        var dependency = getDependency(id);

        if (!dependency) {
            console.error("No dependency found with id " + id);
            return;
        }

        if (imcms.loadedDependencies[id]) {
            console.error("Dependency is already loaded!!!! " + id);
            return;
        }

        imcms.loadedDependencies[id] = true;

        var loader;

        switch (typeof dependency) {
            case "string":
                if (dependency.indexOf(".") !== 0) {
                    dependency = imcms.config.basePath + "/" + dependency;
                }
                loader = appendScript;
                break;
            case "object":
                loader = loadScript;
                break;
            default:
                throw "Dependency type not resolved: " + dependency;
        }

        loader(dependency, id);
    }

    function createXMLHttpRequest() {
        if (typeof XMLHttpRequest !== 'undefined') {
            return new XMLHttpRequest();
        }
        // old browsers support
        var versions = [
            "MSXML2.XmlHttp.6.0",
            "MSXML2.XmlHttp.5.0",
            "MSXML2.XmlHttp.4.0",
            "MSXML2.XmlHttp.3.0",
            "MSXML2.XmlHttp.2.0",
            "Microsoft.XmlHttp"
        ];

        var xhr;
        for (var i = 0; i < versions.length; i++) {
            try {
                xhr = new ActiveXObject(versions[i]);
                break;
            } catch (e) {
            }
        }
        return xhr;
    }

    function getScript(dependency, callback, async) {
        if (async === undefined) {
            async = true;
        }

        var url = dependency.path;
        var ajaxRequest = createXMLHttpRequest();
        ajaxRequest.open("GET", url, async);
        ajaxRequest.overrideMimeType && ajaxRequest.overrideMimeType('application/javascript');
        ajaxRequest.onreadystatechange = function () {
            if (ajaxRequest.readyState !== XMLHttpRequest.DONE) {
                return;
            }

            if (ajaxRequest.status === 200) {
                console.info('%c Script ' + dependency.id + " loaded.", "color: green;");
                var response = eval(ajaxRequest.responseText);
                callback && callback.applyAsync([response]);

            } else {
                console.error('Script get request error: ' + ajaxRequest.status + ' for url: ' + url);
            }
        };
        ajaxRequest.send(null);
    }

    function appendScript(url, id) {
        var script = document.createElement("script");
        script.async = true;
        script.setAttribute("data-loader", "imcms");

        var onLoad = function () {
            console.info('%c Module ' + id + " loaded.", "color: green;");
        };

        if (script.readyState) {  // IE support
            script.onreadystatechange = function () {
                if (script.readyState === "loaded" || script.readyState === "complete") {
                    script.onreadystatechange = null;
                    onLoad();
                }
            };
        } else {  // Other normal browsers
            script.onload = onLoad;
        }

        script.src = url;
        document.getElementsByTagName("head")[0].appendChild(script);
    }

    function resolveDefineArgs() {
        var anonymousModuleId = undefined,
            depsForIndependentModule = [],
            unresolvedArgs = Array.prototype.slice.call(arguments),
            resolvedArgs;

        switch (arguments[0] && arguments[0].constructor) {
            case undefined : // anonymous module with undefined id, nothing to modify
            case String : // means first arg is id, nothing to modify too
                resolvedArgs = unresolvedArgs;
                break;

            case Array : // in this case we have an anonymous module with dependencies array
                resolvedArgs = [anonymousModuleId].concat(unresolvedArgs);
                break;

            case Function : // anonymous independent module
                resolvedArgs = [anonymousModuleId, depsForIndependentModule].concat(unresolvedArgs);
                break;

            default :
                console.error("Can't resolve first argument for 'define'!");
                console.error(arguments);
        }
        switch (resolvedArgs[1] && resolvedArgs[1].constructor) {
            case Array : // dependencies are presented, nothing to change
                break;

            case Function : // independent module and dependencies are not presented
                var factory = resolvedArgs[1];
                resolvedArgs[1] = []; // empty dependencies array
                resolvedArgs[2] = factory;
                break;

            default :
                console.error("Can't resolve second argument for 'define'!");
                console.error(arguments);
        }

        return resolvedArgs;
    }

    /**
     * AMD interface function to catch only imcms-configured modules
     */
    function define() {
        var resolvedArgs = resolveDefineArgs.apply(null, arguments);
        defineModule.applyAsync(resolvedArgs);
    }

    define.amd = {};

    /**
     * AMD define function.
     * Defines module by it's (optional) id with (optional) dependencies
     * by calling factory function after dependencies load.
     *
     * @param {string?} id defined module id
     * @param {[]?} dependencies array as module ids
     * @param {function} factory which return is this defined module in result
     */
    imcms.define = function (id, dependencies, factory) {
        define.applyAsync(arguments);
    };
    /**
     * AMD require function.
     *
     * @param {string|[string]} id required module id(s)
     * @param {function} onLoad function that will be called with loaded required modules
     */
    imcms.require = function (id, onLoad) {
        registerRequires(id, onLoad);
        runModuleLoader.applyAsync();
    };

    function addToDependencyTree(id, dependencies) {
        if (imcms.dependencyTree[id]) {
            console.error("Dependency " + id + " already registered! Redundant define function calling!");
        }
        imcms.dependencyTree[id] = dependencies;
    }

    function defineModule(id, dependencies, factory) {
        if (id) {
            addToDependencyTree(id, dependencies);
        }

        var onDependenciesLoad = function () {
            var module = factory.apply(null, arguments);
            if (id) { // register only not anonymous modules
                registerModule(id, module);
            }
            runModuleLoader.applyAsync();
        };

        imcms.require(dependencies, onDependenciesLoad);
    }

    function registerRequires(id, onLoad) {
        var requires;

        switch (id.constructor) {
            case String :
                requires = [id];
                break;
            case Array :
                requires = id;
                break;
            default :
                console.error("Wrong type: ");
                console.error(id);
        }

        imcms.requiresQueue.push({
            requires: requires,
            onLoad: onLoad
        });
    }

    var failsCount = 0;

    function runModuleLoader() {
        var notSuccessRequiresBuffer = [];

        while (imcms.requiresQueue.length) {
            var require = imcms.requiresQueue.shift();
            var undefinedRequires = require.requires.filter(function (dependency) {
                return !imcms.modules[dependency];
            });

            if (undefinedRequires.length) {
                undefinedRequires = undefinedRequires.filter(function (dependency) {
                    return !imcms.loadedDependencies[dependency];
                });

                if (undefinedRequires.length) {
                    undefinedRequires.forEach(loadDependencyById);
                    notSuccessRequiresBuffer.push(require);

                } else if (failsCount < 5000) {// dummy fail limit
                    // means dependency is still loading script, we should add it to queue after the while cycle
                    failsCount++;
                    notSuccessRequiresBuffer.push(require);

                } else {
                    console.error("Failed to load dependency:");
                    console.error(require);
                }
            } else {
                failsCount = 0;
                var dependencies = require.requires.map(getModule);
                require.onLoad && require.onLoad.applyAsync(dependencies);
            }
        }

        (function () {
            notSuccessRequiresBuffer.forEach(function (require) {
                imcms.requiresQueue.push(require);
            });
            setTimeout(runModuleLoader, 200);
        }).applyAsync();
    }

    function getMainScriptPath() {
        var imcmsMainScripts = Array.prototype.slice.call(document.scripts).filter(function (script) {
            return script.attributes["data-name"] && script.attributes["data-name"].value === "imcms";
        });

        if (!imcmsMainScripts || imcmsMainScripts.length === 0) {
            console.error("Not founded entry point for imCMS JS engine.\n" +
                "Should be script tag with attribute data-name=\"imcms\" and attribute data-main with path to script " +
                "to load as entry point.");
            return;
        }

        if (imcmsMainScripts.length !== 1) {
            console.error("Founded more than one entry points.");
            console.error(imcmsMainScripts);
            return;
        }

        var mainScriptPath = imcmsMainScripts[0].attributes["data-main"].value;
        console.info("%c Found entry point " + mainScriptPath, "color: blue;");
        return mainScriptPath;
    }

    var mainScriptPath = getMainScriptPath();
    appendScript(mainScriptPath, "imcms-main");
})(Imcms);
<commit_msg>IMCMS-323 - Add support for LDAPS and Azure AD: - Changed function definition to prevent it's name minify.<commit_after>console.time("imCMS JS loaded");

Function.prototype.bindArgs = function () {
    return this.bind.apply(this, [null].concat(Array.prototype.slice.call(arguments)));
};
Function.prototype.applyAsync = function (args, context) {
    setTimeout(this.apply.bind(this, context, args));
};
if (!String.prototype.endsWith) {
    Object.defineProperty(String.prototype, 'endsWith', {
        value: function (searchString, position) {
            var subjectString = this.toString();
            if (position === undefined || position > subjectString.length) {
                position = subjectString.length;
            }
            position -= searchString.length;
            var lastIndex = subjectString.indexOf(searchString, position);
            return lastIndex !== -1 && lastIndex === position;
        }
    });
}
if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: function (searchString, position) {
            position = position || 0;
            return this.indexOf(searchString, position) === position;
        }
    });
}
(function (imcms) {
    function registerModule(id, module) {
        if (imcms.modules[id]) {
            console.error("Module already registered! " + id);
            return;
        }

        if (imcms.config.dependencies[id] && imcms.config.dependencies[id].onLoad) {
            module = imcms.config.dependencies[id].onLoad(module);
        }

        imcms.modules[id] = module;
        console.log("%c Module " + id + " registered.", "color: blue;");
        runModuleLoader.applyAsync();
    }

    function getModule(id) {
        return imcms.modules[id];
    }

    function getDependency(id) {
        return imcms.config.dependencies[id];
    }

    function loadScript(dependency, id) {
        var registerFunction;
        dependency.id = id;

        if (dependency.moduleName) {
            registerFunction = registerModule.bindArgs(dependency.moduleName, true);
        }

        var onLoad = function () {
            registerFunction && registerFunction.applyAsync();
            runModuleLoader.applyAsync();
        };

        getScript(dependency, onLoad);
    }

    function loadDependencyById(id) {
        var dependency = getDependency(id);

        if (!dependency) {
            console.error("No dependency found with id " + id);
            return;
        }

        if (imcms.loadedDependencies[id]) {
            console.error("Dependency is already loaded!!!! " + id);
            return;
        }

        imcms.loadedDependencies[id] = true;

        var loader;

        switch (typeof dependency) {
            case "string":
                if (dependency.indexOf(".") !== 0) {
                    dependency = imcms.config.basePath + "/" + dependency;
                }
                loader = appendScript;
                break;
            case "object":
                loader = loadScript;
                break;
            default:
                throw "Dependency type not resolved: " + dependency;
        }

        loader(dependency, id);
    }

    function createXMLHttpRequest() {
        if (typeof XMLHttpRequest !== 'undefined') {
            return new XMLHttpRequest();
        }
        // old browsers support
        var versions = [
            "MSXML2.XmlHttp.6.0",
            "MSXML2.XmlHttp.5.0",
            "MSXML2.XmlHttp.4.0",
            "MSXML2.XmlHttp.3.0",
            "MSXML2.XmlHttp.2.0",
            "Microsoft.XmlHttp"
        ];

        var xhr;
        for (var i = 0; i < versions.length; i++) {
            try {
                xhr = new ActiveXObject(versions[i]);
                break;
            } catch (e) {
            }
        }
        return xhr;
    }

    function getScript(dependency, callback, async) {
        if (async === undefined) {
            async = true;
        }

        var url = dependency.path;
        var ajaxRequest = createXMLHttpRequest();
        ajaxRequest.open("GET", url, async);
        ajaxRequest.overrideMimeType && ajaxRequest.overrideMimeType('application/javascript');
        ajaxRequest.onreadystatechange = function () {
            if (ajaxRequest.readyState !== XMLHttpRequest.DONE) {
                return;
            }

            if (ajaxRequest.status === 200) {
                console.info('%c Script ' + dependency.id + " loaded.", "color: green;");
                var response = eval(ajaxRequest.responseText);
                callback && callback.applyAsync([response]);

            } else {
                console.error('Script get request error: ' + ajaxRequest.status + ' for url: ' + url);
            }
        };
        ajaxRequest.send(null);
    }

    function appendScript(url, id) {
        var script = document.createElement("script");
        script.async = true;
        script.setAttribute("data-loader", "imcms");

        var onLoad = function () {
            console.info('%c Module ' + id + " loaded.", "color: green;");
        };

        if (script.readyState) {  // IE support
            script.onreadystatechange = function () {
                if (script.readyState === "loaded" || script.readyState === "complete") {
                    script.onreadystatechange = null;
                    onLoad();
                }
            };
        } else {  // Other normal browsers
            script.onload = onLoad;
        }

        script.src = url;
        document.getElementsByTagName("head")[0].appendChild(script);
    }

    function resolveDefineArgs() {
        var anonymousModuleId = undefined,
            depsForIndependentModule = [],
            unresolvedArgs = Array.prototype.slice.call(arguments),
            resolvedArgs;

        switch (arguments[0] && arguments[0].constructor) {
            case undefined : // anonymous module with undefined id, nothing to modify
            case String : // means first arg is id, nothing to modify too
                resolvedArgs = unresolvedArgs;
                break;

            case Array : // in this case we have an anonymous module with dependencies array
                resolvedArgs = [anonymousModuleId].concat(unresolvedArgs);
                break;

            case Function : // anonymous independent module
                resolvedArgs = [anonymousModuleId, depsForIndependentModule].concat(unresolvedArgs);
                break;

            default :
                console.error("Can't resolve first argument for 'define'!");
                console.error(arguments);
        }
        switch (resolvedArgs[1] && resolvedArgs[1].constructor) {
            case Array : // dependencies are presented, nothing to change
                break;

            case Function : // independent module and dependencies are not presented
                var factory = resolvedArgs[1];
                resolvedArgs[1] = []; // empty dependencies array
                resolvedArgs[2] = factory;
                break;

            default :
                console.error("Can't resolve second argument for 'define'!");
                console.error(arguments);
        }

        return resolvedArgs;
    }

    /**
     * AMD interface function to catch only imcms-configured modules
     */
    this["define"] = function () {
        var resolvedArgs = resolveDefineArgs.apply(null, arguments);
        defineModule.applyAsync(resolvedArgs);
    };

    this["define"].amd = {};

    /**
     * AMD define function.
     * Defines module by it's (optional) id with (optional) dependencies
     * by calling factory function after dependencies load.
     *
     * @param {string?} id defined module id
     * @param {[]?} dependencies array as module ids
     * @param {function} factory which return is this defined module in result
     */
    imcms.define = function (id, dependencies, factory) {
        define.applyAsync(arguments);
    };
    /**
     * AMD require function.
     *
     * @param {string|[string]} id required module id(s)
     * @param {function} onLoad function that will be called with loaded required modules
     */
    imcms.require = function (id, onLoad) {
        registerRequires(id, onLoad);
        runModuleLoader.applyAsync();
    };

    function addToDependencyTree(id, dependencies) {
        if (imcms.dependencyTree[id]) {
            console.error("Dependency " + id + " already registered! Redundant define function calling!");
        }
        imcms.dependencyTree[id] = dependencies;
    }

    function defineModule(id, dependencies, factory) {
        if (id) {
            addToDependencyTree(id, dependencies);
        }

        var onDependenciesLoad = function () {
            var module = factory.apply(null, arguments);
            if (id) { // register only not anonymous modules
                registerModule(id, module);
            }
            runModuleLoader.applyAsync();
        };

        imcms.require(dependencies, onDependenciesLoad);
    }

    function registerRequires(id, onLoad) {
        var requires;

        switch (id.constructor) {
            case String :
                requires = [id];
                break;
            case Array :
                requires = id;
                break;
            default :
                console.error("Wrong type: ");
                console.error(id);
        }

        imcms.requiresQueue.push({
            requires: requires,
            onLoad: onLoad
        });
    }

    var failsCount = 0;

    function runModuleLoader() {
        var notSuccessRequiresBuffer = [];

        while (imcms.requiresQueue.length) {
            var require = imcms.requiresQueue.shift();
            var undefinedRequires = require.requires.filter(function (dependency) {
                return !imcms.modules[dependency];
            });

            if (undefinedRequires.length) {
                undefinedRequires = undefinedRequires.filter(function (dependency) {
                    return !imcms.loadedDependencies[dependency];
                });

                if (undefinedRequires.length) {
                    undefinedRequires.forEach(loadDependencyById);
                    notSuccessRequiresBuffer.push(require);

                } else if (failsCount < 5000) {// dummy fail limit
                    // means dependency is still loading script, we should add it to queue after the while cycle
                    failsCount++;
                    notSuccessRequiresBuffer.push(require);

                } else {
                    console.error("Failed to load dependency:");
                    console.error(require);
                }
            } else {
                failsCount = 0;
                var dependencies = require.requires.map(getModule);
                require.onLoad && require.onLoad.applyAsync(dependencies);
            }
        }

        (function () {
            notSuccessRequiresBuffer.forEach(function (require) {
                imcms.requiresQueue.push(require);
            });
            setTimeout(runModuleLoader, 200);
        }).applyAsync();
    }

    function getMainScriptPath() {
        var imcmsMainScripts = Array.prototype.slice.call(document.scripts).filter(function (script) {
            return script.attributes["data-name"] && script.attributes["data-name"].value === "imcms";
        });

        if (!imcmsMainScripts || imcmsMainScripts.length === 0) {
            console.error("Not founded entry point for imCMS JS engine.\n" +
                "Should be script tag with attribute data-name=\"imcms\" and attribute data-main with path to script " +
                "to load as entry point.");
            return;
        }

        if (imcmsMainScripts.length !== 1) {
            console.error("Founded more than one entry points.");
            console.error(imcmsMainScripts);
            return;
        }

        var mainScriptPath = imcmsMainScripts[0].attributes["data-main"].value;
        console.info("%c Found entry point " + mainScriptPath, "color: blue;");
        return mainScriptPath;
    }

    var mainScriptPath = getMainScriptPath();
    appendScript(mainScriptPath, "imcms-main");
})(Imcms);
<|endoftext|>


<commit_before>/* eslint-env node, mocha */
var assert = require('assert');
var React = require('react');

var redux = require('hops-redux');
var hopsReact = require('hops-react');

describe('redux', function () {
  var defaultOptions = {
    someSlice: function () {
      return null;
    }
  };

  function setupContext (options) {
    return hopsReact.combineContexts(redux.contextDefinition)(
      Object.assign({}, defaultOptions, options || {})
    );
  }

  it('allows to set middlewares via option', function () {
    var called = false;
    var context = setupContext({
      middlewares: [
        function () {
          return function () {
            return function () {
              called = true;
            };
          };
        }
      ]
    });

    return context
      .enhanceElement(React.createElement('span'))
      .then(function (element) {
        element.props.store.dispatch({ type: '' });
        assert.ok(called);
      });
  });

  it('throws an error, when middlewares is provided but not an array', function () {
    assert.throws(function () {
      setupContext({
        middlewares: function () {}
      });
    });
  });
});
<commit_msg>test(hops-redux): simplify tests<commit_after>/* eslint-env node, mocha */
var assert = require('assert');

var hopsRedux = require('hops-redux');

describe('redux', function () {
  it('allows to set middlewares via option', function () {
    var middleware = function () {};
    var context = new hopsRedux.contextDefinition({
      middlewares: [middleware]
    });
    assert.equal(context.getMiddlewares()[0], middleware);
  });

  it('throws array when middlewares is not an array', function () {
    assert.throws(function () {
      new hopsRedux.contextDefinition({
        middlewares: function () {}
      });
    });
  });
});
<|endoftext|>


<commit_before>/** @namespace */
var THREEx	= THREEx 		|| {};

// TODO http://29a.ch/2011/9/11/uploading-from-html5-canvas-to-imgur-data-uri
// able to upload your screenshot without running servers

// forced closure
(function(){

	/**
	 * Take a screenshot of a renderer
	 * - require WebGLRenderer to have "preserveDrawingBuffer: true" to be set
	 * - TODO is it possible to check if this variable is set ? if so check it
	 *   and make advice in the console.log
	 *   - maybe with direct access to the gl context...
	 *
	 * @param {Object} renderer to use
	 * @param {String} mimetype of the output image. default to "image/png"
	 * @param {String} dataUrl of the image
	*/
	var toDataURL	= function(renderer, mimetype)
	{
		mimetype	= mimetype	|| "image/png";
		var dataUrl	= renderer.domElement.toDataURL(mimetype);
		return dataUrl;
	}

	/**
	 * resize an image to another resolution while preserving aspect
	 *
	 * @param {String} srcUrl the url of the image to resize
	 * @param {Number} dstWidth the destination width of the image
	 * @param {Number} dstHeight the destination height of the image
	 * @param {Number} callback the callback to notify once completed with callback(newImageUrl)
	*/
	var _aspectResize	= function(srcUrl, dstW, dstH, callback){
		// to compute the width/height while keeping aspect
		var cpuScaleAspect	= function(maxW, maxH, curW, curH){
			var ratio	= curH / curW;
			if( curW >= maxW && ratio <= 1 ){
				curW	= maxW;
				curH	= maxW * ratio;
			}else if(curH >= maxH){
				curH	= maxH;
				curW	= maxH / ratio;
			}
			return { width: curW, height: curH };
		}
		// callback once the image is loaded
		var onLoad	= function(){
			// init the canvas
			var canvas	= document.createElement('canvas');
			canvas.width	= dstW;	canvas.height	= dstH;
			var ctx		= canvas.getContext('2d');

			// TODO is this needed
			ctx.fillStyle	= "black";
			ctx.fillRect(0, 0, canvas.width, canvas.height);

			// scale the image while preserving the aspect
			var scaled	= cpuScaleAspect(canvas.width, canvas.height, image.width, image.height);

			// actually draw the image on canvas
			var offsetX	= (canvas.width  - scaled.width )/2;
			var offsetY	= (canvas.height - scaled.height)/2;
			ctx.drawImage(image, offsetX, offsetY, scaled.width, scaled.height);

			// dump the canvas to an URL
			var mimetype	= "image/png";
			var newDataUrl	= canvas.toDataURL(mimetype);
			// notify the url to the caller
			callback && callback(newDataUrl)
		}.bind(this);

		// Create new Image object
		var image 	= new Image();
		image.onload	= onLoad;
		image.src	= srcUrl;
	}

	// Super cooked function: THREEx.Screenshot.bindKey(renderer)
	// and you are done to get screenshot on your demo

	/**
	 * Bind a key to renderer screenshot
	*/
	var bindKey	= function(renderer, opts){
		// handle parameters
		opts		= opts		|| {};
// FIXME this modification of opts parameters is a bug. remove it
		opts.charCode	= opts.charCode	|| 'p'.charCodeAt(0);
		opts.width	= opts.width	|| 640;
		opts.height	= opts.height	|| 480;
		opts.callback	= opts.callback	|| function(url){
			window.open(url);
		};

		// callback to handle keypress
		var onKeyPress	= function(event){
			// return now if the KeyPress isnt for the proper charCode
			if( event.which !== opts.charCode )	return;
			// get the renderer output
			var dataUrl	= this.toDataURL(renderer);
// FIXME dont resize if not explicitly asked
// * resize == async so if callback is a window open, it triggers the pop blocker
			// resize it and notify the callback
			_aspectResize(dataUrl, opts.width, opts.height, opts.callback);
		}.bind(this);

		// listen to keypress
		// NOTE: for firefox it seems mandatory to listen to document directly
		document.addEventListener('keypress', onKeyPress, false);

		return {
			unbind	: function(){
				document.removeEventListener('keypress', onKeyPress, false);
			}
		};
	}

	// export it
	THREEx.Screenshot	= {
		toDataURL	: toDataURL,
		bindKey		: bindKey
	};
})();
<commit_msg>THREEx.screenshot.js made resize optional<commit_after>/** @namespace */
var THREEx	= THREEx 		|| {};

// TODO http://29a.ch/2011/9/11/uploading-from-html5-canvas-to-imgur-data-uri
// able to upload your screenshot without running servers

// forced closure
(function(){

	/**
	 * Take a screenshot of a renderer
	 * - require WebGLRenderer to have "preserveDrawingBuffer: true" to be set
	 * - TODO is it possible to check if this variable is set ? if so check it
	 *   and make advice in the console.log
	 *   - maybe with direct access to the gl context...
	 *
	 * @param {Object} renderer to use
	 * @param {String} mimetype of the output image. default to "image/png"
	 * @param {String} dataUrl of the image
	*/
	var toDataURL	= function(renderer, mimetype)
	{
		mimetype	= mimetype	|| "image/png";
		var dataUrl	= renderer.domElement.toDataURL(mimetype);
		return dataUrl;
	}

	/**
	 * resize an image to another resolution while preserving aspect
	 *
	 * @param {String} srcUrl the url of the image to resize
	 * @param {Number} dstWidth the destination width of the image
	 * @param {Number} dstHeight the destination height of the image
	 * @param {Number} callback the callback to notify once completed with callback(newImageUrl)
	*/
	var _aspectResize	= function(srcUrl, dstW, dstH, callback){
		// to compute the width/height while keeping aspect
		var cpuScaleAspect	= function(maxW, maxH, curW, curH){
			var ratio	= curH / curW;
			if( curW >= maxW && ratio <= 1 ){
				curW	= maxW;
				curH	= maxW * ratio;
			}else if(curH >= maxH){
				curH	= maxH;
				curW	= maxH / ratio;
			}
			return { width: curW, height: curH };
		}
		// callback once the image is loaded
		var onLoad	= function(){
			// init the canvas
			var canvas	= document.createElement('canvas');
			canvas.width	= dstW;	canvas.height	= dstH;
			var ctx		= canvas.getContext('2d');

			// TODO is this needed
			ctx.fillStyle	= "black";
			ctx.fillRect(0, 0, canvas.width, canvas.height);

			// scale the image while preserving the aspect
			var scaled	= cpuScaleAspect(canvas.width, canvas.height, image.width, image.height);

			// actually draw the image on canvas
			var offsetX	= (canvas.width  - scaled.width )/2;
			var offsetY	= (canvas.height - scaled.height)/2;
			ctx.drawImage(image, offsetX, offsetY, scaled.width, scaled.height);

			// dump the canvas to an URL
			var mimetype	= "image/png";
			var newDataUrl	= canvas.toDataURL(mimetype);
			// notify the url to the caller
			callback && callback(newDataUrl)
		}.bind(this);

		// Create new Image object
		var image 	= new Image();
		image.onload	= onLoad;
		image.src	= srcUrl;
	}

	// Super cooked function: THREEx.Screenshot.bindKey(renderer)
	// and you are done to get screenshot on your demo

	/**
	 * Bind a key to renderer screenshot
	*/
	var bindKey	= function(renderer, opts){
		// handle parameters
		opts		= opts		|| {};
		var charCode	= opts.charCode	|| 'p'.charCodeAt(0);
		var width	= opts.width;
		var height	= opts.height;
		var callback	= opts.callback	|| function(url){
			window.open(url, "name-"+Math.random());
		};

		// callback to handle keypress
		var onKeyPress	= function(event){
			// return now if the KeyPress isnt for the proper charCode
			if( event.which !== charCode )	return;
			// get the renderer output
			var dataUrl	= this.toDataURL(renderer);

			if( width === undefined && height === undefined ){
				callback( dataUrl )
			}else{
				// resize it and notify the callback
				// * resize == async so if callback is a window open, it triggers the pop blocker
				_aspectResize(dataUrl, width, height, callback);
			}
		}.bind(this);

		// listen to keypress
		// NOTE: for firefox it seems mandatory to listen to document directly
		document.addEventListener('keypress', onKeyPress, false);

		return {
			unbind	: function(){
				document.removeEventListener('keypress', onKeyPress, false);
			}
		};
	}

	// export it
	THREEx.Screenshot	= {
		toDataURL	: toDataURL,
		bindKey		: bindKey
	};
})();
<|endoftext|>


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

// Example:
//     node lighthouse-core/scripts/compare-runs.js --name my-collection --collect -n 3 --lh-flags='--only-audits=unminified-javascript' --urls https://www.example.com https://www.nyt.com
//     node lighthouse-core/scripts/compare-runs.js --name my-collection --summarize --measure-filter 'loadPage|connect'
//     node lighthouse-core/scripts/compare-runs.js --name base --name pr --compare

// The script will report both timings and perf metric results. View just one of them but using --filter:
//     node lighthouse-core/scripts/compare-runs.js --summarize --name pr --filter=metric

const fs = require('fs');
const mkdir = fs.promises.mkdir;
const glob = require('glob');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const yargs = require('yargs');

const {ProgressLogger} = require('./lantern/collect/common.js');

const LH_ROOT = `${__dirname}/../..`;
const ROOT_OUTPUT_DIR = `${LH_ROOT}/timings-data`;

const argv = yargs
  .help('help')
  .describe({
    // common flags
    'name': 'Unique identifier, makes the folder for storing LHRs. Not a path',
    'report-exclude': 'Regex of properties to exclude. Set to "none" to disable default',
    'collect': 'Gathers, audits and saves LHRs to disk',
    // --gather
    'gather': 'Just gathers',
    'lh-flags': 'Lighthouse flags',
    'urls': 'Urls to run',
    'n': 'Number of times to run',
    'audit': 'Audits from the artifacts on disk',
    // --summarize
    'summarize': 'Prints statistics report',
    'filter': 'Regex inclusion filter applied to key. Optional',
    'reportExclude': 'Regex of columns keys to exclude.',
    'output': 'table, json',
    // --compare
    'compare': 'Compare two sets of LHRs',
    'delta-property-sort': 'Property to sort by its delta',
    'desc': 'Set to override default ascending sort',
  })
  .string('filter')
  .alias({'gather': 'G', 'audit': 'A'})
  .default('report-exclude', 'key|min|max|stdev|^n$')
  .default('delta-property-sort', 'mean')
  .default('output', 'table')
  .array('urls')
  .string('lh-flags')
  .default('desc', false)
  .default('lh-flags', '')
  .strict() // fail on unknown commands
  .wrap(yargs.terminalWidth())
.argv;

const reportExcludeRegex =
  argv.reportExclude !== 'none' ? new RegExp(argv.reportExclude, 'i') : null;

/**
 * @param {string} name
 */
function dir(name) {
  return `${ROOT_OUTPUT_DIR}/${name}`;
}

/**
 * @param {number[]} values
 */
function sum(values) {
  return values.reduce((sum, value) => sum + value);
}

/**
 * @param {number[]} values
 */
function average(values) {
  return sum(values) / values.length;
}

/**
 * @param {number[]} values
 */
function sampleStdev(values) {
  const mean = average(values);
  const variance = sum(values.map(value => (value - mean) ** 2)) / (values.length - 1);
  return Math.sqrt(variance);
}

/**
 * @param {string} url
 * @return string
 */
function urlToFolder(url) {
  return url.replace(/[^a-zA-Z0-9]/g, '_');
}

/**
 * Round to the tenth.
 * @param {number} value
 */
function round(value) {
  return Math.round(value * 10) / 10;
}

/**
 * Get box-drawing progress bar
 * @param {number} i
 * @param {number} total
 * @return {string}
 */
function getProgressBar(i, total = argv.n * argv.urls.length) {
  return new Array(Math.round(i * 40 / total)).fill('▄').join('').padEnd(40);
}

async function gather() {
  const outputDir = dir(argv.name);
  await mkdir(ROOT_OUTPUT_DIR, {recursive: true});
  // Don't overwrite a previous collection
  if (fs.existsSync(outputDir)) throw new Error(`folder already exists: ${outputDir}`);
  await mkdir(outputDir);

  const progress = new ProgressLogger();
  progress.log('Gathering…');

  for (const url of argv.urls) {
    for (let i = 0; i < argv.n; i++) {
      const gatherDir = `${outputDir}/${urlToFolder(url)}/${i}/`;
      await mkdir(gatherDir, {recursive: true});
      progress.progress(getProgressBar(i));

      const cmd = [
        'node',
        `${LH_ROOT}/lighthouse-cli`,
        url,
        `--gather-mode=${gatherDir}`,
        argv.lhFlags,
      ].join(' ');
      await exec(cmd);
    }
  }
  progress.closeProgress();
}

async function audit() {
  const outputDir = dir(argv.name);
  const progress = new ProgressLogger();
  progress.log('Auditing…');

  for (const url of argv.urls) {
    for (let i = 0; i < argv.n; i++) {
      const gatherDir = `${outputDir}/${urlToFolder(url)}/${i}/`;
      progress.progress(getProgressBar(i));

      const cmd = [
        'node',
        `${LH_ROOT}/lighthouse-cli`,
        url,
        `--audit-mode=${gatherDir}`,
        `--output-path=${outputDir}/lhr-${urlToFolder(url)}-${i}.json`,
        '--output=json',
        argv.lhFlags,
      ].join(' ');
      await exec(cmd);
    }
  }
  progress.closeProgress();
}

/**
 * @param {string} name
 */
function aggregateResults(name) {
  const outputDir = dir(name);

  // `${url}@@@${entry.name}` -> duration
  /** @type {Map<string, number[]>} */
  const durationsMap = new Map();

  for (const lhrPath of glob.sync(`${outputDir}/*.json`)) {
    const lhrJson = fs.readFileSync(lhrPath, 'utf-8');
    /** @type {LH.Result} */
    const lhr = JSON.parse(lhrJson);

    const metrics = lhr.audits.metrics ?
    /** @type {!LH.Audit.Details.Table} */ (lhr.audits.metrics.details).items[0] :
      {};
    const allEntries = {
      metric: Object.entries(metrics).filter(([name]) => !name.endsWith('Ts')),
      timing: lhr.timing.entries.map(entry => ([entry.name, entry.duration])),
    };

    Object.entries(allEntries).forEach(([kind, entries]) => {
      // Group the durations of each entry of the same name.
      /** @type {Record<string, number[]>} */
      const durationsByName = {};

      for (const [name, duration] of entries) {
        const durations = durationsByName[name] = durationsByName[name] || [];
        durations.push(Number(duration));
      }

      // Push the aggregate time of each unique (by name) entry.
      for (const [name, durationsForSingleRun] of Object.entries(durationsByName)) {
        const key = `${lhr.requestedUrl}@@@${kind}@@@${name}`;
        let durations = durationsMap.get(key);
        if (!durations) {
          durations = [];
          durationsMap.set(key, durations);
        }
        durations.push(sum(durationsForSingleRun));
      }
    });
  }

  return [...durationsMap].map(([key, durations]) => {
    const [url, _, entryName] = key.split('@@@');
    const mean = average(durations);
    const min = Math.min(...durations);
    const max = Math.max(...durations);
    const stdev = sampleStdev(durations);
    return {
      key,
      name: entryName,
      url,
      n: durations.length,
      mean: round(mean),
      stdev: round(stdev),
      // https://en.wikipedia.org/wiki/Coefficient_of_variation
      CV: `${(stdev / mean * 100).toLocaleString()}%`,
      min: round(min),
      max: round(max),
    };
  }).sort((a, b) => {
    // sort by {measure, url}
    const measureComp = a.name.localeCompare(b.name);
    if (measureComp !== 0) return measureComp;
    return a.url.localeCompare(b.url);
  });
}

/**
 * @param {*[]} results
 */
function filter(results) {
  const includeFilter = argv.filter ? new RegExp(argv.filter, 'i') : null;

  results.forEach((result, i) => {
    for (const propName of Object.keys(result)) {
      if (reportExcludeRegex && reportExcludeRegex.test(propName)) delete result[propName];
    }

    if (includeFilter && !includeFilter.test(result.key)) {
      delete results[i];
    }
  });
}

/**
 * @param {number=} value
 * @return {value is number}
 */
function isNumber(value) {
  return typeof value === 'number';
}

function summarize() {
  const results = aggregateResults(argv.name);
  filter(results);
  print(results);
}

/**
 * @param {number=} base
 * @param {number=} other
 */
function compareValues(base, other) {
  const basePart = isNumber(base) ? base : 'N/A';
  const otherPart = isNumber(other) ? other : 'N/A';
  return {
    description: `${basePart} -> ${otherPart}`,
    delta: isNumber(base) && isNumber(other) ? (other - base) : undefined,
  };
}

function compare() {
  if (!Array.isArray(argv.name) || argv.name.length !== 2) {
    throw new Error('expected two entries for name option');
  }

  const baseResults = aggregateResults(argv.name[0]);
  const otherResults = aggregateResults(argv.name[1]);

  const keys = [...new Set([...baseResults.map(r => r.key), ...otherResults.map(r => r.key)])];
  const results = keys.map(key => {
    const baseResult = baseResults.find(result => result.key === key);
    const otherResult = otherResults.find(result => result.key === key);

    const someResult = baseResult || otherResult;
    if (!someResult) throw new Error('impossible');

    const mean = compareValues(baseResult && baseResult.mean, otherResult && otherResult.mean);
    const stdev = compareValues(baseResult && baseResult.stdev, otherResult && otherResult.stdev);
    // eslint-disable-next-line max-len
    const cv = compareValues(baseResult && parseFloat(baseResult.CV), otherResult && parseFloat(otherResult.CV));
    const min = compareValues(baseResult && baseResult.min, otherResult && otherResult.min);
    const max = compareValues(baseResult && baseResult.max, otherResult && otherResult.max);

    return {
      'name': someResult.name,
      'url': someResult.url,
      'mean': mean.description,
      'mean Δ': isNumber(mean.delta) ? round(mean.delta) : undefined,
      'stdev': stdev.description,
      'stdev Δ': isNumber(stdev.delta) ? round(stdev.delta) : undefined,
      'cv': cv.description,
      'cv Δ': isNumber(cv.delta) ? round(cv.delta) : undefined,
      'min': min.description,
      'min Δ': isNumber(min.delta) ? round(min.delta) : undefined,
      'max': max.description,
      'max Δ': isNumber(max.delta) ? round(max.delta) : undefined,
    };
  });

  const sortByKey = `${argv.deltaPropertySort} Δ`;
  results.sort((a, b) => {
    // @ts-ignore - shhh tsc.
    const aValue = a[sortByKey];
    // @ts-ignore - shhh tsc.
    const bValue = b[sortByKey];

    // Always put the keys missing a result at the bottom of the table.
    if (!isNumber(aValue)) return 1;
    else if (!isNumber(bValue)) return -1;

    return (argv.desc ? 1 : -1) * (Math.abs(aValue) - Math.abs(bValue));
  });
  filter(results);
  print(results);
}

/**
 * @param {*[]} results
 */
function print(results) {
  if (argv.output === 'table') {
    // eslint-disable-next-line no-console
    console.table(results);
  } else if (argv.output === 'json') {
    // eslint-disable-next-line no-console
    console.log(JSON.stringify(results, null, 2));
  }
}

async function main() {
  if (argv.gather) await gather();
  if (argv.audit) await audit();
  if (argv.collect) {
    await gather();
    await audit();
  }
  if (argv.summarize) summarize();
  if (argv.compare) compare();
}

main();
<commit_msg>misc(compare-runs): fix filter, allow for resume, reorganize output<commit_after>/**
 * @license Copyright 2019 The Lighthouse Authors. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

// Example:
//     node lighthouse-core/scripts/compare-runs.js --name my-collection --collect -n 3 --lh-flags='--only-audits=unminified-javascript' --urls https://www.example.com https://www.nyt.com
//     node lighthouse-core/scripts/compare-runs.js --name my-collection --summarize --filter 'loadPage|connect'
//     node lighthouse-core/scripts/compare-runs.js --name base --name pr --compare

// The script will report both timings and perf metric results. View just one of them but using --filter:
//     node lighthouse-core/scripts/compare-runs.js --summarize --name pr --filter=metric

const fs = require('fs');
const mkdir = fs.promises.mkdir;
const glob = require('glob');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const yargs = require('yargs');

const {ProgressLogger} = require('./lantern/collect/common.js');

const LH_ROOT = `${__dirname}/../..`;
const ROOT_OUTPUT_DIR = `${LH_ROOT}/timings-data`;

const argv = yargs
  .help('help')
  .describe({
    // common flags
    'name': 'Unique identifier, makes the folder for storing LHRs. Not a path',
    'report-exclude': 'Regex of properties to exclude. Set to "none" to disable default',
    'collect': 'Gathers, audits and saves LHRs to disk',
    // --gather
    'gather': 'Just gathers',
    'lh-flags': 'Lighthouse flags',
    'urls': 'Urls to run',
    'n': 'Number of times to run',
    'audit': 'Audits from the artifacts on disk',
    // --summarize
    'summarize': 'Prints statistics report',
    'filter': 'Regex inclusion filter applied to key. Optional',
    'reportExclude': 'Regex of columns keys to exclude.',
    'output': 'table, json',
    // --compare
    'compare': 'Compare two sets of LHRs',
    'delta-property-sort': 'Property to sort by its delta',
    'desc': 'Set to override default ascending sort',
  })
  .string('filter')
  .alias({'gather': 'G', 'audit': 'A'})
  .default('report-exclude', 'key|min|max|stdev|^n$')
  .default('delta-property-sort', 'mean')
  .default('output', 'table')
  .array('urls')
  .string('lh-flags')
  .default('desc', false)
  .default('lh-flags', '')
  .strict() // fail on unknown commands
  .wrap(yargs.terminalWidth())
  .argv;

const reportExcludeRegex =
  argv.reportExclude !== 'none' ? new RegExp(argv.reportExclude, 'i') : null;

/**
 * @param {string} name
 */
function dir(name) {
  return `${ROOT_OUTPUT_DIR}/${name}`;
}

/**
 * @param {number[]} values
 */
function sum(values) {
  return values.reduce((sum, value) => sum + value);
}

/**
 * @param {number[]} values
 */
function average(values) {
  return sum(values) / values.length;
}

/**
 * @param {number[]} values
 */
function sampleStdev(values) {
  const mean = average(values);
  const variance = sum(values.map(value => (value - mean) ** 2)) / (values.length - 1);
  return Math.sqrt(variance);
}

/**
 * @param {string} url
 * @return string
 */
function urlToFolder(url) {
  return url.replace(/[^a-zA-Z0-9]/g, '_');
}

/**
 * Round to the tenth.
 * @param {number} value
 */
function round(value) {
  return Math.round(value * 10) / 10;
}

/**
 * Get box-drawing progress bar
 * @param {number} i
 * @param {number} total
 * @return {string}
 */
function getProgressBar(i, total = argv.n * argv.urls.length) {
  const bars = new Array(Math.round(i * 40 / total)).fill('▄').join('').padEnd(40);
  return `${i + 1} / ${total} [${bars}]`;
}

async function gather() {
  const outputDir = dir(argv.name);
  if (fs.existsSync(outputDir)) {
    // eslint-disable-next-line no-console
    console.log('Collection already started - resuming.');
  }
  await mkdir(outputDir, {recursive: true});

  const progress = new ProgressLogger();
  progress.log('Gathering…');

  let progressCount = 0;
  for (const url of argv.urls) {
    const urlFolder = `${outputDir}/${urlToFolder(url)}`;
    await mkdir(urlFolder, {recursive: true});

    for (let i = 0; i < argv.n; i++) {
      const gatherDir = `${urlFolder}/${i}`;

      progress.progress(getProgressBar(progressCount));
      progressCount++;

      // Skip if already gathered. Allows for restarting collection.
      if (fs.existsSync(gatherDir)) continue;

      const cmd = [
        'node',
        `${LH_ROOT}/lighthouse-cli`,
        url,
        `--gather-mode=${gatherDir}`,
        argv.lhFlags,
      ].join(' ');
      await exec(cmd);
    }
  }
  progress.closeProgress();
}

async function audit() {
  const outputDir = dir(argv.name);
  const progress = new ProgressLogger();
  progress.log('Auditing…');

  let progressCount = 0;
  for (const url of argv.urls) {
    const urlDir = `${outputDir}/${urlToFolder(url)}`;
    for (let i = 0; i < argv.n; i++) {
      const gatherDir = `${urlDir}/${i}`;
      const outputPath = `${urlDir}/lhr-${i}.json`;

      progress.progress(getProgressBar(progressCount));
      progressCount++;

      // Skip if already audited. Allows for restarting collection.
      if (fs.existsSync(outputPath)) continue;

      const cmd = [
        'node',
        `${LH_ROOT}/lighthouse-cli`,
        url,
        `--audit-mode=${gatherDir}`,
        `--output-path=${outputPath}`,
        '--output=json',
        argv.lhFlags,
      ].join(' ');
      await exec(cmd);
    }
  }
  progress.closeProgress();
}

/**
 * @param {string} name
 */
function aggregateResults(name) {
  const outputDir = dir(name);

  // `${url}@@@${entry.name}` -> duration
  /** @type {Map<string, number[]>} */
  const durationsMap = new Map();

  for (const lhrPath of glob.sync(`${outputDir}/**/lhr-*.json`)) {
    const lhrJson = fs.readFileSync(lhrPath, 'utf-8');
    /** @type {LH.Result} */
    const lhr = JSON.parse(lhrJson);

    const metrics = lhr.audits.metrics ?
    /** @type {!LH.Audit.Details.Table} */ (lhr.audits.metrics.details).items[0] :
      {};
    const allEntries = {
      metric: Object.entries(metrics).filter(([name]) => !name.endsWith('Ts')),
      timing: lhr.timing.entries.map(entry => ([entry.name, entry.duration])),
    };

    Object.entries(allEntries).forEach(([kind, entries]) => {
      // Group the durations of each entry of the same name.
      /** @type {Record<string, number[]>} */
      const durationsByName = {};

      for (const [name, duration] of entries) {
        const durations = durationsByName[name] = durationsByName[name] || [];
        durations.push(Number(duration));
      }

      // Push the aggregate time of each unique (by name) entry.
      for (const [name, durationsForSingleRun] of Object.entries(durationsByName)) {
        const key = `${lhr.requestedUrl}@@@${kind}@@@${name}`;
        let durations = durationsMap.get(key);
        if (!durations) {
          durations = [];
          durationsMap.set(key, durations);
        }
        durations.push(sum(durationsForSingleRun));
      }
    });
  }

  return [...durationsMap].map(([key, durations]) => {
    const [url, _, entryName] = key.split('@@@');
    const mean = average(durations);
    const min = Math.min(...durations);
    const max = Math.max(...durations);
    const stdev = sampleStdev(durations);
    return {
      key,
      name: entryName,
      url,
      n: durations.length,
      mean: round(mean),
      stdev: round(stdev),
      // https://en.wikipedia.org/wiki/Coefficient_of_variation
      CV: `${(stdev / mean * 100).toLocaleString()}%`,
      min: round(min),
      max: round(max),
    };
  }).sort((a, b) => {
    // sort by {measure, url}
    const measureComp = a.name.localeCompare(b.name);
    if (measureComp !== 0) return measureComp;
    return a.url.localeCompare(b.url);
  });
}

/**
 * @param {Array<{key: string, name: string}>} results
 */
function filter(results) {
  const includeFilter = argv.filter ? new RegExp(argv.filter, 'i') : null;

  results.forEach((result, i) => {
    if (includeFilter && !includeFilter.test(result.key)) {
      delete results[i];
    }

    for (const propName of Object.keys(result)) {
      if (reportExcludeRegex && reportExcludeRegex.test(propName)) {
        // @ts-ignore: propName is a key.
        delete result[propName];
      }
    }
  });
}

/**
 * @param {number=} value
 * @return {value is number}
 */
function isNumber(value) {
  return typeof value === 'number';
}

function summarize() {
  const results = aggregateResults(argv.name);
  filter(results);
  print(results);
}

/**
 * @param {number=} base
 * @param {number=} other
 */
function compareValues(base, other) {
  const basePart = isNumber(base) ? base : 'N/A';
  const otherPart = isNumber(other) ? other : 'N/A';
  return {
    description: `${basePart} -> ${otherPart}`,
    delta: isNumber(base) && isNumber(other) ? (other - base) : undefined,
  };
}

function compare() {
  if (!Array.isArray(argv.name) || argv.name.length !== 2) {
    throw new Error('expected two entries for name option');
  }

  const baseResults = aggregateResults(argv.name[0]);
  const otherResults = aggregateResults(argv.name[1]);

  const keys = [...new Set([...baseResults.map(r => r.key), ...otherResults.map(r => r.key)])];
  const results = keys.map(key => {
    const baseResult = baseResults.find(result => result.key === key);
    const otherResult = otherResults.find(result => result.key === key);

    const someResult = baseResult || otherResult;
    if (!someResult) throw new Error('impossible');

    const mean = compareValues(baseResult && baseResult.mean, otherResult && otherResult.mean);
    const stdev = compareValues(baseResult && baseResult.stdev, otherResult && otherResult.stdev);
    // eslint-disable-next-line max-len
    const cv = compareValues(baseResult && parseFloat(baseResult.CV), otherResult && parseFloat(otherResult.CV));
    const min = compareValues(baseResult && baseResult.min, otherResult && otherResult.min);
    const max = compareValues(baseResult && baseResult.max, otherResult && otherResult.max);

    return {
      'key': someResult.key,
      'name': someResult.name,
      'url': someResult.url,
      'mean': mean.description,
      'mean Δ': isNumber(mean.delta) ? round(mean.delta) : undefined,
      'stdev': stdev.description,
      'stdev Δ': isNumber(stdev.delta) ? round(stdev.delta) : undefined,
      'cv': cv.description,
      'cv Δ': isNumber(cv.delta) ? round(cv.delta) : undefined,
      'min': min.description,
      'min Δ': isNumber(min.delta) ? round(min.delta) : undefined,
      'max': max.description,
      'max Δ': isNumber(max.delta) ? round(max.delta) : undefined,
    };
  });

  const sortByKey = `${argv.deltaPropertySort} Δ`;
  results.sort((a, b) => {
    // @ts-ignore - shhh tsc.
    const aValue = a[sortByKey];
    // @ts-ignore - shhh tsc.
    const bValue = b[sortByKey];

    // Always put the keys missing a result at the bottom of the table.
    if (!isNumber(aValue)) return 1;
    else if (!isNumber(bValue)) return -1;

    return (argv.desc ? 1 : -1) * (Math.abs(aValue) - Math.abs(bValue));
  });
  filter(results);
  print(results);
}

/**
 * @param {*[]} results
 */
function print(results) {
  if (argv.output === 'table') {
    // eslint-disable-next-line no-console
    console.table(results);
  } else if (argv.output === 'json') {
    // eslint-disable-next-line no-console
    console.log(JSON.stringify(results, null, 2));
  }
}

async function main() {
  if (argv.gather) await gather();
  if (argv.audit) await audit();
  if (argv.collect) {
    await gather();
    await audit();
  }
  if (argv.summarize) summarize();
  if (argv.compare) compare();
}

main();
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, IT Services, Stockholm University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of Stockholm University nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

var config = (function (pgBrokerBaseURL) {
  return {
    "core": {
      "ga": {
        "account": "UA-39850755-1"
      }
    },

    "sukat": {
      "search": {
        "url": pgBrokerBaseURL + "/sukat/search"
      }
    },

    "map": {
      "campuses": {
        "url": pgBrokerBaseURL + "/geo/campuses"
      },
      "location": {
        "url": pgBrokerBaseURL + "/geo/poi"
      },
      "zoom": {
        "threshold": 16
      }
    }
  };
})("http://lucien.it.su.se:8080/hermes-broker");
<commit_msg>correction to the previous commit<commit_after>/*
 * Copyright (c) 2013, IT Services, Stockholm University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of Stockholm University nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

var config = (function (pgBrokerBaseURL) {
  return {
    "core": {
      "ga": {
        "account": "UA-39850755-1"
      }
    },

    "sukat": {
      "search": {
        "url": pgBrokerBaseURL + "/sukat/search"
      }
    },

    "map": {
      "campuses": {
        "url": pgBrokerBaseURL + "/geo/campuses"
      },
      "location": {
        "url": pgBrokerBaseURL + "/geo/poi"
      },
      "zoom": {
        "threshold": 16
      }
    }
  };
})("http://mobileapp-dev.it.su.se");
<|endoftext|>