<commit_before>'use strict';

const DEFAULT_INTENT = "input.unknown";
const DEFAULT_SKILL = "builtin_default";
const DEFAULT_SKILL_PATH = "../../../../skill/";

let Promise = require('bluebird');
let debug = require("debug")("flow");
let apiai = require('apiai');

module.exports = class Flow {
    constructor(vp, bot_event, context, options){
        this.vp = vp;
        this.bot_event = bot_event;
        this.context = context;
        this.default_intent = options.default_intent || DEFAULT_INTENT;
        this.default_skill = options.default_skill || DEFAULT_SKILL;
        this.skill_path = options.skill_path;
        this.skill = this._instantiate_skill(this.context.intent.action);

        if (!!this.skill.required_parameter && typeof this.skill.required_parameter == "object"){
            debug(`This skill requires ${Object.keys(this.skill.required_parameter).length} parameters.`);
        } else {
            debug(`This skill requires 0 parameters.`);
        }
        this.context.to_confirm = this._identify_to_confirm_parameter(this.skill.required_parameter, this.context.confirmed);

        debug(`We have ${Object.keys(this.context.to_confirm).length} parameters to confirm.`);
    }

    _instantiate_skill(intent){
        if (!intent){
            debug("Intent should have been set but not.");
            return;
        }

        let skill;
        // If the intent is not identified, we use default_skill.
        if (intent == this.default_intent){
            skill = this.default_skill;
        } else {
            skill = intent;
        }

        let skill_instance;
        try {
            if (skill == "builtin_default"){
                debug("Use built-in default skill.");
                let skill_class = require("../skill/default");
                skill_instance = new skill_class();
            } else {
                debug(`Use ${skill} skill.`);
                let skill_class = require(`${this.skill_path}${skill}`);
                skill_instance = new skill_class();
            }
        } catch (err){
            debug(`Cannnot import ${this.skill_path}${skill}`);
            debug(err);
            throw(err);
        }
        return skill_instance;
    }

    _identify_to_confirm_parameter(required_parameter, confirmed){
        let to_confirm = {};

        // If there is no required_parameter, we just return empty object as confirmed.
        if (!required_parameter){
            return to_confirm;
        }

        // Scan confirmed parameters and if missing required parameters found, we add them to to_confirm.
        for (let req_param_key of Object.keys(required_parameter)){
            if (typeof confirmed[req_param_key] == "undefined"){
                to_confirm[req_param_key] = required_parameter[req_param_key];
            }
        }
        return to_confirm;
    }

    _collect(){
        if (Object.keys(this.context.to_confirm).length == 0){
            debug("While collect() is called, there is no parameter to confirm.");
            return Promise.reject();
        }
        let messages;
        if (!!this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm[this.vp.type]){
            // Found message platform specific message object.
            debug("Found message platform specific message object.");
            let messages = [this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm[this.vp.type]];
        } else if (!!this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm["common"]){
            // Found common message object. We compile this message object to get message platform specific message object.
            debug("Found common message object.");
            let messages = [this.vp.compile_message(this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm["common"])];
        } else {
            debug("While we need to send a message to confirm parameter, the message not found.");
            return Promise.reject();
        }
        debug(messages);

        // Set confirming.
        this.context.confirming = Object.keys(this.context.to_confirm)[0];

        // Send question to the user.
        return this.vp.reply(this.bot_event, messages);
    }

    change_parameter(key, value){
        this.add_parameter(key, value, true);
    }

    add_parameter(key, value, is_change = false){
        debug(`Parsing parameter {${key}: "${value}"}`);

        let parsed_value;

        // Parse the value. If the value is not suitable for this key, exception will be thrown.
        if (this.skill.required_parameter[key]){
            if (!!this.skill.required_parameter[key].parse){
                parsed_value = this.skill.required_parameter[key].parse(value);
            } else if (!!this.skill["parse_" + key]){
                parsed_value = this.skill["parse_" + key](value);
            } else {
                // If parse method is not implemented, we use the value as-is.
                if (value === null || value == ""){
                    parsed_value = false;
                } else {
                    parsed_value = value;
                }
            }
        } else if (this.skill.optional_parameter[key]){
            if (!!this.skill.optional_parameter[key].parse){
                parsed_value = this.skill.optional_parameter[key].parse(value);
            } else if (!!this.skill["parse_" + key]){
                parsed_value = this.skill["parse_" + key](value);
            } else {
                // If parse method is not implemented, we use the value as-is.
                if (value === null || value == ""){
                    parsed_value = false;
                } else {
                    parsed_value = value;
                }
            }
        } else {
            // This is not the parameter we care about. So skip it.
            debug("This is not the parameter we care about.");
            throw("This is not the parameter we care about.");
        }

        if (parsed_value === false){
            // This means user defined skill says this value does not fit to this parameter.
            throw(`The value does not fit to this parameter.`);
        }

        debug(`Adding parameter {${key}: "${parsed_value}"}`);

        // Add the parameter to "confirmed".
        let param = {};
        param[key] = parsed_value;
        Object.assign(this.context.confirmed, param);

        // At the same time, add the parameter key to previously confirmed list. The order of this list is newest first.
        if (!is_change){
            this.context.previous.confirmed.unshift(key);
        }

        // Remove item from to_confirm.
        if (this.context.to_confirm[key]){
            delete this.context.to_confirm[key];
        }

        // Clear confirming.
        if (this.context.confirming == key){
            this.context.confirming = null;
        }

        debug(`We have ${Object.keys(this.context.to_confirm).length} parameters to confirm.`);
    }

    ask_retry(message_text){
        let messages = [this.vp.create_message(message_text)];
        return this.vp.reply(this.bot_event, messages);
    }

    finish(){
        // If we still have parameters to confirm, we collect them.
        if (Object.keys(this.context.to_confirm).length > 0){
            debug("Going to collect parameter.");
            return this._collect();
        }

        if (this.skill["before_finish"]){
            debug("Going to process before finish.");
            return this.skill["before_finish"](this.vp, this.bot_event, this.context);
        }

        // If we have no parameters to confirm, we finish this conversation using finish method of skill.
        debug("Going to perform final action.");
        return this.skill.finish(this.vp, this.bot_event, this.context).then(
            (response) => {
                if (this.skill.clear_context_on_finish){
                    debug(`Clearing context.`);
                    this.context = null;
                }
                return response;
            },
            (response) => {
                return Promise.reject(response);
            }
        );
    }
};
<commit_msg>added compile_message to vp<commit_after>'use strict';

const DEFAULT_INTENT = "input.unknown";
const DEFAULT_SKILL = "builtin_default";
const DEFAULT_SKILL_PATH = "../../../../skill/";

let Promise = require('bluebird');
let debug = require("debug")("flow");
let apiai = require('apiai');

module.exports = class Flow {
    constructor(vp, bot_event, context, options){
        this.vp = vp;
        this.bot_event = bot_event;
        this.context = context;
        this.default_intent = options.default_intent || DEFAULT_INTENT;
        this.default_skill = options.default_skill || DEFAULT_SKILL;
        this.skill_path = options.skill_path;
        this.skill = this._instantiate_skill(this.context.intent.action);

        if (!!this.skill.required_parameter && typeof this.skill.required_parameter == "object"){
            debug(`This skill requires ${Object.keys(this.skill.required_parameter).length} parameters.`);
        } else {
            debug(`This skill requires 0 parameters.`);
        }
        this.context.to_confirm = this._identify_to_confirm_parameter(this.skill.required_parameter, this.context.confirmed);

        debug(`We have ${Object.keys(this.context.to_confirm).length} parameters to confirm.`);
    }

    _instantiate_skill(intent){
        if (!intent){
            debug("Intent should have been set but not.");
            return;
        }

        let skill;
        // If the intent is not identified, we use default_skill.
        if (intent == this.default_intent){
            skill = this.default_skill;
        } else {
            skill = intent;
        }

        let skill_instance;
        try {
            if (skill == "builtin_default"){
                debug("Use built-in default skill.");
                let skill_class = require("../skill/default");
                skill_instance = new skill_class();
            } else {
                debug(`Use ${skill} skill.`);
                let skill_class = require(`${this.skill_path}${skill}`);
                skill_instance = new skill_class();
            }
        } catch (err){
            debug(`Cannnot import ${this.skill_path}${skill}`);
            debug(err);
            throw(err);
        }
        return skill_instance;
    }

    _identify_to_confirm_parameter(required_parameter, confirmed){
        let to_confirm = {};

        // If there is no required_parameter, we just return empty object as confirmed.
        if (!required_parameter){
            return to_confirm;
        }

        // Scan confirmed parameters and if missing required parameters found, we add them to to_confirm.
        for (let req_param_key of Object.keys(required_parameter)){
            if (typeof confirmed[req_param_key] == "undefined"){
                to_confirm[req_param_key] = required_parameter[req_param_key];
            }
        }
        return to_confirm;
    }

    _collect(){
        if (Object.keys(this.context.to_confirm).length == 0){
            debug("While collect() is called, there is no parameter to confirm.");
            return Promise.reject();
        }
        let messages;
        if (!!this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm[this.vp.type]){
            // Found message platform specific message object.
            debug("Found message platform specific message object.");
            messages = [this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm[this.vp.type]];
        } else if (!!this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm["common"]){
            // Found common message object. We compile this message object to get message platform specific message object.
            debug("Found common message object.");
            messages = [this.vp.compile_message(this.context.to_confirm[Object.keys(this.context.to_confirm)[0]].message_to_confirm["common"])];
        } else {
            debug("While we need to send a message to confirm parameter, the message not found.");
            return Promise.reject();
        }
        debug(messages);

        // Set confirming.
        this.context.confirming = Object.keys(this.context.to_confirm)[0];

        // Send question to the user.
        return this.vp.reply(this.bot_event, messages);
    }

    change_parameter(key, value){
        this.add_parameter(key, value, true);
    }

    add_parameter(key, value, is_change = false){
        debug(`Parsing parameter {${key}: "${value}"}`);

        let parsed_value;

        // Parse the value. If the value is not suitable for this key, exception will be thrown.
        if (this.skill.required_parameter[key]){
            if (!!this.skill.required_parameter[key].parse){
                parsed_value = this.skill.required_parameter[key].parse(value);
            } else if (!!this.skill["parse_" + key]){
                parsed_value = this.skill["parse_" + key](value);
            } else {
                // If parse method is not implemented, we use the value as-is.
                if (value === null || value == ""){
                    parsed_value = false;
                } else {
                    parsed_value = value;
                }
            }
        } else if (this.skill.optional_parameter[key]){
            if (!!this.skill.optional_parameter[key].parse){
                parsed_value = this.skill.optional_parameter[key].parse(value);
            } else if (!!this.skill["parse_" + key]){
                parsed_value = this.skill["parse_" + key](value);
            } else {
                // If parse method is not implemented, we use the value as-is.
                if (value === null || value == ""){
                    parsed_value = false;
                } else {
                    parsed_value = value;
                }
            }
        } else {
            // This is not the parameter we care about. So skip it.
            debug("This is not the parameter we care about.");
            throw("This is not the parameter we care about.");
        }

        if (parsed_value === false){
            // This means user defined skill says this value does not fit to this parameter.
            throw(`The value does not fit to this parameter.`);
        }

        debug(`Adding parameter {${key}: "${parsed_value}"}`);

        // Add the parameter to "confirmed".
        let param = {};
        param[key] = parsed_value;
        Object.assign(this.context.confirmed, param);

        // At the same time, add the parameter key to previously confirmed list. The order of this list is newest first.
        if (!is_change){
            this.context.previous.confirmed.unshift(key);
        }

        // Remove item from to_confirm.
        if (this.context.to_confirm[key]){
            delete this.context.to_confirm[key];
        }

        // Clear confirming.
        if (this.context.confirming == key){
            this.context.confirming = null;
        }

        debug(`We have ${Object.keys(this.context.to_confirm).length} parameters to confirm.`);
    }

    ask_retry(message_text){
        let messages = [this.vp.create_message(message_text)];
        return this.vp.reply(this.bot_event, messages);
    }

    finish(){
        // If we still have parameters to confirm, we collect them.
        if (Object.keys(this.context.to_confirm).length > 0){
            debug("Going to collect parameter.");
            return this._collect();
        }

        if (this.skill["before_finish"]){
            debug("Going to process before finish.");
            return this.skill["before_finish"](this.vp, this.bot_event, this.context);
        }

        // If we have no parameters to confirm, we finish this conversation using finish method of skill.
        debug("Going to perform final action.");
        return this.skill.finish(this.vp, this.bot_event, this.context).then(
            (response) => {
                if (this.skill.clear_context_on_finish){
                    debug(`Clearing context.`);
                    this.context = null;
                }
                return response;
            },
            (response) => {
                return Promise.reject(response);
            }
        );
    }
};
<|endoftext|>


<commit_before>/* @flow weak */
/**
 * @memberof Marbles
 * @mixin
 * @desc Manages a state object. You must define `state` {Object} and `__changeListeners` {Array} on the object this is mixed into.
 */
var State = {
	/**
	 * @method
	 * @param {function} handler Function to call when the state object changes
	 */
	addChangeListener: function (handler) {
		this.__changeListeners.push(handler);
	},

	/**
	 * @method
	 * @param {function} handler
	 * @desc Prevents handler from being called for future changes
	 */
	removeChangeListener: function (handler) {
		this.__changeListeners = this.__changeListeners.filter(function (fn) {
			return fn !== handler;
		});
	},

	/**
	 * @method
	 * @param {function} changeFn
	 * @desc Calls `willChange`, the passed in function, `handleChange`, then `didChange`
	 */
	withChange: function (changeFn) {
		this.willChange();
		changeFn.call(this);
		this.handleChange();
		this.didChange();
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Copies properties of newState to the existing state object
	 */
	setState: function (newState) {
		this.withChange(function () {
			var state = this.state;
			Object.keys(newState).forEach(function (key) {
				state[key] = newState[key];
			});
		});
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Same as setState, but waits up to 10ms for more changes to occur before calling change listeners
	 */
	setStateWithDelay: function (newState) {
		this.willChange();
		var state = this.state;
		Object.keys(newState).forEach(function (key) {
			state[key] = newState[key];
		});
		this.handleChangeWithDelay();
	},

	handleChangeWithDelay: function () {
		clearTimeout(this.__handleChangeTimeout);
		this.__handleChangeTimeout = setTimeout(function () {
			this.handleChangeDelayed();
		}.bind(this), 2);
		if ( !this.__handleChangeMaxTimeout ) {
			this.__handleChangeMaxTimeout = setTimeout(function () {
				this.handleChangeDelayed();
			}.bind(this), 10);
		}
	},

	handleChangeDelayed: function () {
		clearTimeout(this.__handleChangeMaxTimeout);
		clearTimeout(this.__handleChangeTimeout);
		this.handleChange();
		this.didChange();
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Replaces the existing state object with newState
	 */
	replaceState: function (newState) {
		this.withChange(function () {
			this.state = newState;
		});
	},

	handleChange: function () {
		this.__changeListeners.forEach(function (handler) {
			handler();
		});
	},

	/**
	 * @method
	 * @desc Called before state object is mutated
	 */
	willChange: function () {},

	/**
	 * @method
	 * @desc Called after state object is mutated
	 */
	didChange: function () {}
};

export default State;
<commit_msg>Allow setting max timeout for setStateWithDelay<commit_after>/* @flow weak */
/**
 * @memberof Marbles
 * @mixin
 * @desc Manages a state object. You must define `state` {Object} and `__changeListeners` {Array} on the object this is mixed into.
 */
var State = {
	/**
	 * @method
	 * @param {function} handler Function to call when the state object changes
	 */
	addChangeListener: function (handler) {
		this.__changeListeners.push(handler);
	},

	/**
	 * @method
	 * @param {function} handler
	 * @desc Prevents handler from being called for future changes
	 */
	removeChangeListener: function (handler) {
		this.__changeListeners = this.__changeListeners.filter(function (fn) {
			return fn !== handler;
		});
	},

	/**
	 * @method
	 * @param {function} changeFn
	 * @desc Calls `willChange`, the passed in function, `handleChange`, then `didChange`
	 */
	withChange: function (changeFn) {
		this.willChange();
		changeFn.call(this);
		this.handleChange();
		this.didChange();
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Copies properties of newState to the existing state object
	 */
	setState: function (newState) {
		this.withChange(function () {
			var state = this.state;
			Object.keys(newState).forEach(function (key) {
				state[key] = newState[key];
			});
		});
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @param {Number} maxTimeout
	 * @desc Same as setState, but waits up to 10ms for more changes to occur before calling change listeners
	 */
	setStateWithDelay: function (newState, maxTimeout) {
		this.willChange();
		var state = this.state;
		Object.keys(newState).forEach(function (key) {
			state[key] = newState[key];
		});
		this.handleChangeWithDelay(maxTimeout);
	},

	handleChangeWithDelay: function (maxTimeout) {
		maxTimeout = maxTimeout || 10;
		clearTimeout(this.__handleChangeTimeout);
		this.__handleChangeTimeout = setTimeout(function () {
			this.handleChangeDelayed();
		}.bind(this), 2);
		if ( !this.__handleChangeMaxTimeout ) {
			this.__handleChangeMaxTimeout = setTimeout(function () {
				this.handleChangeDelayed();
			}.bind(this), maxTimeout);
		}
	},

	handleChangeDelayed: function () {
		clearTimeout(this.__handleChangeMaxTimeout);
		clearTimeout(this.__handleChangeTimeout);
		this.handleChange();
		this.didChange();
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Replaces the existing state object with newState
	 */
	replaceState: function (newState) {
		this.withChange(function () {
			this.state = newState;
		});
	},

	handleChange: function () {
		this.__changeListeners.forEach(function (handler) {
			handler();
		});
	},

	/**
	 * @method
	 * @desc Called before state object is mutated
	 */
	willChange: function () {},

	/**
	 * @method
	 * @desc Called after state object is mutated
	 */
	didChange: function () {}
};

export default State;
<|endoftext|>


<commit_before>L.Control.MatrixLayers = L.Control.Layers.extend({

	initialize: function (baseLayers, overlays, matrixOverlays, options) {
		L.Control.Layers.prototype.initialize.call(this, baseLayers, overlays, options);

		for (layerName in matrixOverlays) {
			this._addMatrixOverlay(matrixOverlays[layerName], layerName);
		}
	},

	onAdd: function (map) {
		var container = L.Control.Layers.prototype.onAdd.call(this, map);

		this._updateSelectedLayers();

		return container;
	},

	_update: function () {
		//much borrowed from https://github.com/Leaflet/Leaflet/blob/59a8c00a1850103f4fba8561961282eb21b29e7d/src/control/Control.Layers.js#L132
		if (!this._container) {
			return;
		}

		this._baseLayersList.innerHTML = '';
		this._overlaysList.innerHTML = '';

		var baseLayersPresent = false,
		    overlaysPresent = false,
		    i, obj;

		//get hold of all the dimension values
		var allDimensions = [];
		for (i in this._layers) {
			obj = this._layers[i];
			var layerName = obj.name;

			if (this._matrixLayers !== undefined && layerName in this._matrixLayers) {
				var dimensionElements = layerName.split('/');
				for (var i = 0; i < dimensionElements.length; i++) {
					var dimensionElement = dimensionElements[i];
					if (allDimensions[i] == undefined) {
						allDimensions[i] = {};
					}
					allDimensions[i][dimensionElement] = true;
				}
			} else {
				this._addItem(obj);
				overlaysPresent = overlaysPresent || obj.overlay;
				baseLayersPresent = baseLayersPresent || !obj.overlay;
			}
		}

		//now create the UI for all dimensions
		for (var i = 0; i < allDimensions.length; i++) {
			var dimensionName = this.options.dimensionNames[i];
			var dimension = allDimensions[i];
			var parentElement = document.createElement('div');
			parentElement.className = 'dimension-container';
			parentElement.dimensionName = dimensionName;

			var dimensionDisplay = dimensionName;
			if (this.options.dimensionLabels != null) {
				var display = this.options.dimensionLabels[dimensionName];
				if (display != null) {
					dimensionDisplay = display;
				}
			}

			var dimensionEl = document.createElement('div');
			dimensionEl.innerHTML = '' + dimensionDisplay + ' (all / none)';
			var context = this;

			function selectAll(parentElement, all) {
				var checkboxes = parentElement.getElementsByTagName('input');
				for (var i = 0; i < checkboxes.length; i++) {
					checkboxes[i].checked = all;
					context._saveSelectionState(checkboxes[i].dimensionName, checkboxes[i].dimensionValue, all);
				}
				context._updateLayerClick(parentElement);//once after updating all
			}

			var checkAllHref = dimensionEl.querySelector('a.check-all');
			var checkNoneHref = dimensionEl.querySelector('a.check-none');
			(function(parentElement) {//scoping
				checkAllHref.onclick = function() {
					selectAll(parentElement, true);
					return false;
				}
				checkNoneHref.onclick = function() {
					selectAll(parentElement, false);
					return false;
				}
			})(parentElement);
			parentElement.appendChild(dimensionEl);

			this._overlaysList.appendChild(parentElement);
			Object.keys(dimension).forEach(function (dimensionValue) {
				this._addMatrixItem(parentElement, dimensionName, dimensionValue);
			}, this);
		}

	},

	_addMatrixOverlay: function (layer, name) {
		if (this._matrixLayers == undefined) {
			this._matrixLayers = {};
		}
		this._matrixLayers[name] = layer;
		this.addOverlay(layer, name);
	},

	_onMatrixInputClick: function (e) {
		this._handlingClick = true;

		var input = e.currentTarget;
		this._saveSelectionState(input.dimensionName, input.dimensionValue, input.checked);
		var labelElement = input.parentElement;
		this._updateLayerClick(labelElement);
	},

	_updateLayerClick: function(labelElement) {
		//the whirly loading image never worked very well, so just disable everything instead
		this._setOverlaysDisablement(true);
		//give the UI chance to update
		setTimeout(function() {
			this._updateSelectedLayers(function() {
				this._setOverlaysDisablement(false);
			}.bind(this));
		}.bind(this));
	},

	_updateSelectedLayers: function(callback) {
		//reset - onInputClick might have got there before us
		this._handlingClick = true;

		//get the inputs in each div and see which are ticked, to get a list of the selected dimension elements
		var divs = this._overlaysList.getElementsByTagName('div');
		var checkedDimensions = {};
		for (var i = 0; i < divs.length; i++) {
			var div = divs[i];
			var dimension = div.dimensionName;
			var inputs = div.getElementsByTagName('input');
			if (checkedDimensions[dimension] == undefined) {
				checkedDimensions[dimension] = new Array();
			}
			for (var j = 0; j < inputs.length; j++) {
				var input = inputs[j];
				if (input.checked) {
					checkedDimensions[dimension].push(input.dimensionId);
				}
			}
		}
		//map the group dimension names to an ordering
		var orderedDimensionElements = new Array();
		for (var i = 0; i < this.options.dimensionNames.length; i++) {
			var dimensionName = this.options.dimensionNames[i];
			var dimensionElements = checkedDimensions[dimensionName];
			orderedDimensionElements.push(dimensionElements);
		}
		//iterate to construct a list of selected layers
		var selectedLayerNames = {};
		this._depthFirstIteration(orderedDimensionElements, 0, "", function(path){
			selectedLayerNames[path] = true;
		});
		//now add or remove the layers from the map
		Object.keys(this._matrixLayers).forEach(function (layerName) {
			var layer = this._matrixLayers[layerName];
			if (layerName in selectedLayerNames && !this._map.hasLayer(layer)) {
				this._map.addLayer(layer);

			} else if (!(layerName in selectedLayerNames) && this._map.hasLayer(layer)) {
				this._map.removeLayer(layer);
			}
		}, this);

		if (callback !== undefined) {
			callback();
		}

		this._handlingClick = false;

		this._refocusOnMap();
	},

	_depthFirstIteration: function (dimensions, dimensionIndex, parentPath, found) {
		var currentDimension = dimensions[dimensionIndex];
		if (currentDimension != undefined) {
			for (var i = 0; i < currentDimension.length; i++) {
				var dimensionValue = currentDimension[i];
				var newPath = dimensionValue;
				if (parentPath.length > 0) {
					newPath = parentPath + '/' + newPath;
				}
				var newIndex = dimensionIndex + 1;
				this._depthFirstIteration(dimensions, newIndex, newPath, found);
			}
		} else {
			found(parentPath);
		}
	},

	_addMatrixItem: function (parentElement, dimensionName, dimensionValue) {
		var label = document.createElement('label');

		var input = document.createElement('input');
		input.type = 'checkbox';
		input.className = 'leaflet-control-layers-selector';
		input.checked = this._getSelectionState(dimensionName, dimensionValue);
		input.dimensionId = dimensionValue;//L.stamp(obj.layer);//TODO//dimensionName + "::" +
		input.dimensionName = dimensionName;
		input.dimensionValue = dimensionValue;

		L.DomEvent.on(input, 'click', this._onMatrixInputClick, this);

		var displayString = dimensionValue;
		if (this.options.dimensionValueLabels != null && this.options.dimensionValueLabels[dimensionName] != null && this.options.dimensionValueLabels[dimensionName][dimensionValue] != null) {
			displayString = this.options.dimensionValueLabels[dimensionName][dimensionValue];
		}
		var display = document.createElement('span');
		display.innerHTML = ' ' + displayString;

		label.appendChild(input);
		label.appendChild(display);

		parentElement.appendChild(label);

		return label;
	},

	//we have to override this to stop it finding our inputs and then struggling to find layers for them
	_onInputClick: function () {
		var i, input, obj,
		    inputs = this._form.getElementsByTagName('input'),
		    inputsLen = inputs.length;

		this._handlingClick = true;

		for (i = 0; i < inputsLen; i++) {
			input = inputs[i];
			if (input.dimensionId === undefined) {//check this isn't a matrix layer
				obj = this._layers[input.layerId];

				if (input.checked && !this._map.hasLayer(obj.layer)) {
					this._map.addLayer(obj.layer);

				} else if (!input.checked && this._map.hasLayer(obj.layer)) {
					this._map.removeLayer(obj.layer);
				}
			}
		}

		this._handlingClick = false;

		this._refocusOnMap();
	},

	_setOverlaysDisablement: function(disabled) {
		var nodes = document.querySelectorAll('div.leaflet-control-layers-overlays *');
		for (var i = 0; i < nodes.length; i++) {
			nodes[i].disabled = disabled;
			if (disabled) {
				nodes[i].classList.add('disabled');
			} else {
				nodes[i].classList.remove('disabled');
			}
		}
	},

	_saveSelectionState: function(dimensionName, dimensionValue, selected) {
		if (localStorage !== undefined) {
			localStorage.setItem('matrix_layers_control:' + dimensionName + '/' + dimensionValue, selected.toString());
		}
	},

	_getSelectionState: function(dimensionName, dimensionValue) {
		if (localStorage !== undefined) {
			var selected = localStorage.getItem('matrix_layers_control:' + dimensionName + '/' + dimensionValue);
			if (selected == null) {
				return true;//default to being selected
			} else {
				return "true" == selected;//local storage can only store strings
			}
		} else {
			return true;//default to being selected
		}
	}

});

L.control.matrixLayers = function (baseLayers, overlays, matrixOverlays, options) {
	return new L.Control.MatrixLayers(baseLayers, overlays, matrixOverlays, options);
};
<commit_msg>Adding UMD to work better with AMD<commit_after>function factory(leaflet) {
	var matrixLayers = leaflet.Control.Layers.extend({

		initialize: function (baseLayers, overlays, matrixOverlays, options) {
			leaflet.Control.Layers.prototype.initialize.call(this, baseLayers, overlays, options);

			for (layerName in matrixOverlays) {
				this._addMatrixOverlay(matrixOverlays[layerName], layerName);
			}
		},

		onAdd: function (map) {
			var container = leaflet.Control.Layers.prototype.onAdd.call(this, map);

			this._updateSelectedLayers();

			return container;
		},

		_update: function () {
			//much borrowed from https://github.com/Leaflet/Leaflet/blob/59a8c00a1850103f4fba8561961282eb21b29e7d/src/control/Control.Layers.js#L132
			if (!this._container) {
				return;
			}

			this._baseLayersList.innerHTML = '';
			this._overlaysList.innerHTML = '';

			var baseLayersPresent = false,
				overlaysPresent = false,
				i, obj;

			//get hold of all the dimension values
			var allDimensions = [];
			for (i in this._layers) {
				obj = this._layers[i];
				var layerName = obj.name;

				if (this._matrixLayers !== undefined && layerName in this._matrixLayers) {
					var dimensionElements = layerName.split('/');
					for (var i = 0; i < dimensionElements.length; i++) {
						var dimensionElement = dimensionElements[i];
						if (allDimensions[i] == undefined) {
							allDimensions[i] = {};
						}
						allDimensions[i][dimensionElement] = true;
					}
				} else {
					this._addItem(obj);
					overlaysPresent = overlaysPresent || obj.overlay;
					baseLayersPresent = baseLayersPresent || !obj.overlay;
				}
			}

			//now create the UI for all dimensions
			for (var i = 0; i < allDimensions.length; i++) {
				var dimensionName = this.options.dimensionNames[i];
				var dimension = allDimensions[i];
				var parentElement = document.createElement('div');
				parentElement.className = 'dimension-container';
				parentElement.dimensionName = dimensionName;

				var dimensionDisplay = dimensionName;
				if (this.options.dimensionLabels != null) {
					var display = this.options.dimensionLabels[dimensionName];
					if (display != null) {
						dimensionDisplay = display;
					}
				}

				var dimensionEl = document.createElement('div');
				dimensionEl.innerHTML = '' + dimensionDisplay + ' (all / none)';
				var context = this;

				function selectAll(parentElement, all) {
					var checkboxes = parentElement.getElementsByTagName('input');
					for (var i = 0; i < checkboxes.length; i++) {
						checkboxes[i].checked = all;
						context._saveSelectionState(checkboxes[i].dimensionName, checkboxes[i].dimensionValue, all);
					}
					context._updateLayerClick(parentElement);//once after updating all
				}

				var checkAllHref = dimensionEl.querySelector('a.check-all');
				var checkNoneHref = dimensionEl.querySelector('a.check-none');
				(function(parentElement) {//scoping
					checkAllHref.onclick = function() {
						selectAll(parentElement, true);
						return false;
					}
					checkNoneHref.onclick = function() {
						selectAll(parentElement, false);
						return false;
					}
				})(parentElement);
				parentElement.appendChild(dimensionEl);

				this._overlaysList.appendChild(parentElement);
				Object.keys(dimension).forEach(function (dimensionValue) {
					this._addMatrixItem(parentElement, dimensionName, dimensionValue);
				}, this);
			}

		},

		_addMatrixOverlay: function (layer, name) {
			if (this._matrixLayers == undefined) {
				this._matrixLayers = {};
			}
			this._matrixLayers[name] = layer;
			this.addOverlay(layer, name);
		},

		_onMatrixInputClick: function (e) {
			this._handlingClick = true;

			var input = e.currentTarget;
			this._saveSelectionState(input.dimensionName, input.dimensionValue, input.checked);
			var labelElement = input.parentElement;
			this._updateLayerClick(labelElement);
		},

		_updateLayerClick: function(labelElement) {
			//the whirly loading image never worked very well, so just disable everything instead
			this._setOverlaysDisablement(true);
			//give the UI chance to update
			setTimeout(function() {
				this._updateSelectedLayers(function() {
					this._setOverlaysDisablement(false);
				}.bind(this));
			}.bind(this));
		},

		_updateSelectedLayers: function(callback) {
			//reset - onInputClick might have got there before us
			this._handlingClick = true;

			//get the inputs in each div and see which are ticked, to get a list of the selected dimension elements
			var divs = this._overlaysList.getElementsByTagName('div');
			var checkedDimensions = {};
			for (var i = 0; i < divs.length; i++) {
				var div = divs[i];
				var dimension = div.dimensionName;
				var inputs = div.getElementsByTagName('input');
				if (checkedDimensions[dimension] == undefined) {
					checkedDimensions[dimension] = new Array();
				}
				for (var j = 0; j < inputs.length; j++) {
					var input = inputs[j];
					if (input.checked) {
						checkedDimensions[dimension].push(input.dimensionId);
					}
				}
			}
			//map the group dimension names to an ordering
			var orderedDimensionElements = new Array();
			for (var i = 0; i < this.options.dimensionNames.length; i++) {
				var dimensionName = this.options.dimensionNames[i];
				var dimensionElements = checkedDimensions[dimensionName];
				orderedDimensionElements.push(dimensionElements);
			}
			//iterate to construct a list of selected layers
			var selectedLayerNames = {};
			this._depthFirstIteration(orderedDimensionElements, 0, "", function(path){
				selectedLayerNames[path] = true;
			});
			//now add or remove the layers from the map
			Object.keys(this._matrixLayers).forEach(function (layerName) {
				var layer = this._matrixLayers[layerName];
				if (layerName in selectedLayerNames && !this._map.hasLayer(layer)) {
					this._map.addLayer(layer);

				} else if (!(layerName in selectedLayerNames) && this._map.hasLayer(layer)) {
					this._map.removeLayer(layer);
				}
			}, this);

			if (callback !== undefined) {
				callback();
			}

			this._handlingClick = false;

			this._refocusOnMap();
		},

		_depthFirstIteration: function (dimensions, dimensionIndex, parentPath, found) {
			var currentDimension = dimensions[dimensionIndex];
			if (currentDimension != undefined) {
				for (var i = 0; i < currentDimension.length; i++) {
					var dimensionValue = currentDimension[i];
					var newPath = dimensionValue;
					if (parentPath.length > 0) {
						newPath = parentPath + '/' + newPath;
					}
					var newIndex = dimensionIndex + 1;
					this._depthFirstIteration(dimensions, newIndex, newPath, found);
				}
			} else {
				found(parentPath);
			}
		},

		_addMatrixItem: function (parentElement, dimensionName, dimensionValue) {
			var label = document.createElement('label');

			var input = document.createElement('input');
			input.type = 'checkbox';
			input.className = 'leaflet-control-layers-selector';
			input.checked = this._getSelectionState(dimensionName, dimensionValue);
			input.dimensionId = dimensionValue;//L.stamp(obj.layer);//TODO//dimensionName + "::" +
			input.dimensionName = dimensionName;
			input.dimensionValue = dimensionValue;

			leaflet.DomEvent.on(input, 'click', this._onMatrixInputClick, this);

			var displayString = dimensionValue;
			if (this.options.dimensionValueLabels != null && this.options.dimensionValueLabels[dimensionName] != null && this.options.dimensionValueLabels[dimensionName][dimensionValue] != null) {
				displayString = this.options.dimensionValueLabels[dimensionName][dimensionValue];
			}
			var display = document.createElement('span');
			display.innerHTML = ' ' + displayString;

			label.appendChild(input);
			label.appendChild(display);

			parentElement.appendChild(label);

			return label;
		},

		//we have to override this to stop it finding our inputs and then struggling to find layers for them
		_onInputClick: function () {
			var i, input, obj,
				inputs = this._form.getElementsByTagName('input'),
				inputsLen = inputs.length;

			this._handlingClick = true;

			for (i = 0; i < inputsLen; i++) {
				input = inputs[i];
				if (input.dimensionId === undefined) {//check this isn't a matrix layer
					obj = this._layers[input.layerId];

					if (input.checked && !this._map.hasLayer(obj.layer)) {
						this._map.addLayer(obj.layer);

					} else if (!input.checked && this._map.hasLayer(obj.layer)) {
						this._map.removeLayer(obj.layer);
					}
				}
			}

			this._handlingClick = false;

			this._refocusOnMap();
		},

		_setOverlaysDisablement: function(disabled) {
			var nodes = document.querySelectorAll('div.leaflet-control-layers-overlays *');
			for (var i = 0; i < nodes.length; i++) {
				nodes[i].disabled = disabled;
				if (disabled) {
					nodes[i].classList.add('disabled');
				} else {
					nodes[i].classList.remove('disabled');
				}
			}
		},

		_saveSelectionState: function(dimensionName, dimensionValue, selected) {
			if (localStorage !== undefined) {
				localStorage.setItem('matrix_layers_control:' + dimensionName + '/' + dimensionValue, selected.toString());
			}
		},

		_getSelectionState: function(dimensionName, dimensionValue) {
			if (localStorage !== undefined) {
				var selected = localStorage.getItem('matrix_layers_control:' + dimensionName + '/' + dimensionValue);
				if (selected == null) {
					return true;//default to being selected
				} else {
					return "true" == selected;//local storage can only store strings
				}
			} else {
				return true;//default to being selected
			}
		}

	});

	return matrixLayers;
}

//umd
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['leaflet'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require('leaflet'));
    } else {
        root.L.Control.MatrixLayers = factory(root.L);
		root.L.control.matrixLayers = function (baseLayers, overlays, matrixOverlays, options) {
			return new root.L.Control.MatrixLayers(baseLayers, overlays, matrixOverlays, options);
};
    }
}(this, factory));
<|endoftext|>


<commit_before>import Handlebars from 'handlebars';
import dataLoader from './utils/dataLoader';
import helpersLoader from './utils/helpersLoader';
import partialsLoader from './utils/partialsLoader';
import {
  getAbsolutePath,
  getBasename,
  getPathDiff,
  resolveGlob,
  readFile,
  registerDependency,
  getBasenameWithoutExt,
} from './utils/tools';

class IungoWebpackPlugin {
  constructor(options = {}) {
    this.options = Object.assign(
      {
        entry: null,
        output: null,
        data: [],
        helpers: {},
        partials: [],
        htmlWebpackPlugin: null,
        onBeforeInit: Function.prototype,
        onBeforeRegisterHelpers: Function.prototype,
        onBeforeRegisterPartials: Function.prototype,
        onBeforeCompile: Function.prototype,
        onBeforeRender: Function.prototype,
        onBeforeSave: Function.prototype,
        onDone: Function.prototype,
      },
      options,
    );

    this.options.entry = getAbsolutePath(this.options.entry);
    this.options.output = getAbsolutePath(this.options.output);

    this.options.onBeforeInit(Handlebars);
    this.data = {};
    this.fileDependencies = [];
    this.assetsGenerated = {};
    this.prevTimestamp = {};
    this.startTime = Date.now();
  }

  loadData() {
    dataLoader(this.options.data, this.data, this.fileDependencies);
  }

  loadHelpers() {
    helpersLoader(
      this.options.helpers,
      this.fileDependencies,
      this.options.onBeforeRegisterHelpers,
    );
  }

  loadPartials() {
    partialsLoader(
      this.options.partials,
      this.fileDependencies,
      this.options.onBeforeRegisterPartials,
    );
  }

  apply(compiler) {
    const compile = (compilation, callback) => {
      try {
        if (!this.dependenciesUpdated(compilation)) {
          callback();
          return;
        }

        this.loadData();
        this.loadHelpers();
        this.loadPartials();

        const paths = resolveGlob(`${this.options.entry}/**/*.html`);
        if (paths.length === 0) {
          callback();
          return;
        }
        paths.forEach((path) => {
          const content = readFile(path);
          this.compileFile(path, content, compilation);
        });
        callback();
      } catch (error) {
        compilation.errors.push(new Error(`IungoWebpackPlugin: ${error.message}`));
        callback();
      }
    };

    const compileWithHtmlPlugin = (compilation, callback, data) => {
      try {
        if (!this.dependenciesUpdated(compilation)) {
          callback();
          return;
        }

        this.loadData();
        this.loadHelpers();
        this.loadPartials();

        let path = data.plugin.options.template.split('!').pop();
        let content = data.html;

        this.options.onBeforeSave = function(hb, hbTemplate) {
          data.html = hbTemplate;
        };

        this.compileFile(path, content, compilation);
        callback();
      } catch (error) {
        compilation.errors.push(new Error(`IungoWebpackPlugin: ${error.message}`));
        callback();
      }
    };

    const registerDependencies = (compilation, callback) => {
      try {
        this.fileDependencies.forEach(
          compilation.fileDependencies.add,
          compilation.fileDependencies,
        );
        Object.keys(this.assetsGenerated).forEach((filename) => {
          compilation.assets[filename] = this.assetsGenerated[filename];
        });
        callback();
      } catch (error) {
        compilation.errors.push(
          new Error('IungoWebpackPlugin: there was an error registering dependencies'),
        );
        callback();
      }
    };

    if (this.options.htmlWebpackPlugin) {
      compiler.hooks.compilation.tap('IungoWebpackPlugin', (compilation) => {
        this.options.htmlWebpackPlugin
          .getHooks(compilation)
          .beforeEmit.tapAsync('IungoWebpackPlugin', (data, callback) => {
            compileWithHtmlPlugin(
              compilation,
              () => {
                registerDependencies(compilation, callback);
              },
              data,
            );
          });
      });
    } else {
      compiler.hooks.make.tapAsync('IungoWebpackPlugin', compile);
      compiler.hooks.emit.tapAsync('IungoWebpackPlugin', registerDependencies);
    }
  }

  dependenciesUpdated(compilation) {
    const timestampsSnapshot = compilation.fileTimestamps;
    const filePaths = Array.from(timestampsSnapshot.keys());

    const changedFiles = filePaths.filter((filePath) => {
      const prevTimestamp = this.prevTimestamp[filePath];
      const nextTimestamp = timestampsSnapshot.get(filePath);
      this.prevTimestamp[filePath] = nextTimestamp;

      return (prevTimestamp || this.startTime) < (nextTimestamp || Infinity);
    });

    if (changedFiles.length === 0) {
      return true;
    }

    for (let i = 0; i < changedFiles.length; i++) {
      if (this.fileDependencies.includes(changedFiles[i])) {
        return true;
      }
    }
    return false;
  }

  compileFile(path, content, compilation) {
    let targetPath;
    if (this.options.output) {
      targetPath = this.options.output.replace('[name]', getBasenameWithoutExt(path));
    } else {
      targetPath = `${compilation.compiler.outputPath}/${getBasename(path)}`;
    }

    const templateContent = this.options.onBeforeCompile(Handlebars, content) || content;
    const templateCompiled = Handlebars.compile(templateContent);
    const dataToRender = this.options.onBeforeRender(Handlebars, this.data) || this.data;
    let templateRendered = templateCompiled(dataToRender);
    templateRendered = this.options.onBeforeSave(Handlebars, templateRendered) || templateRendered;

    registerDependency(path, this.fileDependencies);

    if (!this.options.htmlWebpackPlugin) {
      if (targetPath.includes(compilation.compiler.outputPath)) {
        this.assetsGenerated[getPathDiff(targetPath, compilation.compiler.outputPath)] = {
          source: () => templateRendered,
          size: () => templateRendered.length,
        };
      } else {
        compilation.errors.push(
          new Error(
            "IungoWebpackPlugin: you can't set the output filepath outside the webpack dest folder",
          ),
        );
      }
    }

    this.options.onDone(Handlebars, getPathDiff(targetPath, compilation.compiler.outputPath));
  }
}

export { IungoWebpackPlugin };
<commit_msg>UPDATED: HtmlWebpackPlugin hook used<commit_after>import Handlebars from 'handlebars';
import dataLoader from './utils/dataLoader';
import helpersLoader from './utils/helpersLoader';
import partialsLoader from './utils/partialsLoader';
import {
  getAbsolutePath,
  getBasename,
  getPathDiff,
  resolveGlob,
  readFile,
  registerDependency,
  getBasenameWithoutExt,
} from './utils/tools';

class IungoWebpackPlugin {
  constructor(options = {}) {
    this.options = Object.assign(
      {
        entry: null,
        output: null,
        data: [],
        helpers: {},
        partials: [],
        htmlWebpackPlugin: null,
        onBeforeInit: Function.prototype,
        onBeforeRegisterHelpers: Function.prototype,
        onBeforeRegisterPartials: Function.prototype,
        onBeforeCompile: Function.prototype,
        onBeforeRender: Function.prototype,
        onBeforeSave: Function.prototype,
        onDone: Function.prototype,
      },
      options,
    );

    this.options.entry = getAbsolutePath(this.options.entry);
    this.options.output = getAbsolutePath(this.options.output);

    this.options.onBeforeInit(Handlebars);
    this.data = {};
    this.fileDependencies = [];
    this.assetsGenerated = {};
    this.prevTimestamp = {};
    this.startTime = Date.now();
  }

  loadData() {
    dataLoader(this.options.data, this.data, this.fileDependencies);
  }

  loadHelpers() {
    helpersLoader(
      this.options.helpers,
      this.fileDependencies,
      this.options.onBeforeRegisterHelpers,
    );
  }

  loadPartials() {
    partialsLoader(
      this.options.partials,
      this.fileDependencies,
      this.options.onBeforeRegisterPartials,
    );
  }

  apply(compiler) {
    const compile = (compilation, callback) => {
      try {
        if (!this.dependenciesUpdated(compilation)) {
          callback();
          return;
        }

        this.loadData();
        this.loadHelpers();
        this.loadPartials();

        const paths = resolveGlob(`${this.options.entry}/**/*.html`);
        if (paths.length === 0) {
          callback();
          return;
        }
        paths.forEach((path) => {
          const content = readFile(path);
          this.compileFile(path, content, compilation);
        });
        callback();
      } catch (error) {
        compilation.errors.push(new Error(`IungoWebpackPlugin: ${error.message}`));
        callback();
      }
    };

    const compileWithHtmlPlugin = (compilation, callback, data) => {
      try {
        if (!this.dependenciesUpdated(compilation)) {
          callback();
          return;
        }

        this.loadData();
        this.loadHelpers();
        this.loadPartials();

        let path = data.plugin.options.template.split('!').pop();
        let content = data.html;

        this.options.onBeforeSave = function(hb, hbTemplate) {
          data.html = hbTemplate;
        };

        this.compileFile(path, content, compilation);
        callback();
      } catch (error) {
        compilation.errors.push(new Error(`IungoWebpackPlugin: ${error.message}`));
        callback();
      }
    };

    const registerDependencies = (compilation, callback) => {
      try {
        this.fileDependencies.forEach(
          compilation.fileDependencies.add,
          compilation.fileDependencies,
        );
        Object.keys(this.assetsGenerated).forEach((filename) => {
          compilation.assets[filename] = this.assetsGenerated[filename];
        });
        callback();
      } catch (error) {
        compilation.errors.push(
          new Error('IungoWebpackPlugin: there was an error registering dependencies'),
        );
        callback();
      }
    };

    if (this.options.htmlWebpackPlugin) {
      compiler.hooks.compilation.tap('IungoWebpackPlugin', (compilation) => {
        this.options.htmlWebpackPlugin
          .getHooks(compilation)
          .afterTemplateExecution.tapAsync('IungoWebpackPlugin', (data, callback) => {
            compileWithHtmlPlugin(
              compilation,
              () => {
                registerDependencies(compilation, callback);
              },
              data,
            );
          });
      });
    } else {
      compiler.hooks.make.tapAsync('IungoWebpackPlugin', compile);
      compiler.hooks.emit.tapAsync('IungoWebpackPlugin', registerDependencies);
    }
  }

  dependenciesUpdated(compilation) {
    const timestampsSnapshot = compilation.fileTimestamps;
    const filePaths = Array.from(timestampsSnapshot.keys());

    const changedFiles = filePaths.filter((filePath) => {
      const prevTimestamp = this.prevTimestamp[filePath];
      const nextTimestamp = timestampsSnapshot.get(filePath);
      this.prevTimestamp[filePath] = nextTimestamp;

      return (prevTimestamp || this.startTime) < (nextTimestamp || Infinity);
    });

    if (changedFiles.length === 0) {
      return true;
    }

    for (let i = 0; i < changedFiles.length; i++) {
      if (this.fileDependencies.includes(changedFiles[i])) {
        return true;
      }
    }
    return false;
  }

  compileFile(path, content, compilation) {
    let targetPath;
    if (this.options.output) {
      targetPath = this.options.output.replace('[name]', getBasenameWithoutExt(path));
    } else {
      targetPath = `${compilation.compiler.outputPath}/${getBasename(path)}`;
    }

    const templateContent = this.options.onBeforeCompile(Handlebars, content) || content;
    const templateCompiled = Handlebars.compile(templateContent);
    const dataToRender = this.options.onBeforeRender(Handlebars, this.data) || this.data;
    let templateRendered = templateCompiled(dataToRender);
    templateRendered = this.options.onBeforeSave(Handlebars, templateRendered) || templateRendered;

    registerDependency(path, this.fileDependencies);

    if (!this.options.htmlWebpackPlugin) {
      if (targetPath.includes(compilation.compiler.outputPath)) {
        this.assetsGenerated[getPathDiff(targetPath, compilation.compiler.outputPath)] = {
          source: () => templateRendered,
          size: () => templateRendered.length,
        };
      } else {
        compilation.errors.push(
          new Error(
            "IungoWebpackPlugin: you can't set the output filepath outside the webpack dest folder",
          ),
        );
      }
    }

    this.options.onDone(Handlebars, getPathDiff(targetPath, compilation.compiler.outputPath));
  }
}

export { IungoWebpackPlugin };
<|endoftext|>


<commit_before>// Find location name
//
// In:
//  - redis set `geo:location` (`lon:lat:locale`)
//  - redis set `geo:location:fast` (`lon:lat:locale`)
//

'use strict';

const _       = require('lodash');
const got     = require('got');
const got_pkg = require('got/package.json');

module.exports = function (N) {
  let overpass_last_request  = 0;
  let nominatim_last_request = 0;

  let rootUrl = _.get(N.config, 'bind.default.mount', 'http://localhost') + '/';
  let userAgent = `${got_pkg.name}/${got_pkg.version} (Nodeca; +${rootUrl})`;

  // Get location name using overpass `is_in` operator (fetches a place
  // defined as an area that cover specified location).
  //
  async function resolve_location_overpass(lat, lon, locale) {
    let req_id = N.models.core.GeoOverpass.hash([ lon, lat ]);

    let cached = await N.models.core.GeoOverpass.findOne({ hash: req_id }).lean(true);
    let body;

    if (cached) {
      if (cached.error || !cached.result) return null;

      body = JSON.parse(cached.result);
    } else {
      let now = Date.now();

      // Limit to 1 request per second, this should keep us below 5GB/day limit
      // https://wiki.openstreetmap.org/wiki/Overpass_API
      //
      // In case it hits their limits too often, you can check free slots here:
      // http://overpass-api.de/api/status
      //
      if (Math.abs(now - overpass_last_request) < 1000) {
        await new Promise(resolve => setTimeout(resolve, 1000 - Math.abs(now - overpass_last_request)));
      }

      overpass_last_request = Date.now();

      let script = `
        [timeout:5][out:json];
        is_in(${lat},${lon})->.a;
        area.a[place~"city|town|village|hamlet"];
        out 1;
      `.replace(/\s+/g, ' ');

      let response, error;

      try {
        response = await got('http://overpass-api.de/api/interpreter', {
          query:   { data: script },
          headers: { 'user-agent': userAgent },
          timeout: 15000,
          retry:   1
        });

        body = JSON.parse(response.body);
      } catch (err) {
        // keep 400 errors (syntax error in the request), re-throw the rest
        if (err.statusCode !== 400) throw err;

        error = err;
      }

      await N.models.core.GeoOverpass.updateOne(
        { hash: req_id },
        { $set: _.omitBy({
          location:   [ lon, lat ],
          result:     error ? null : response && response.body,
          error:      error ? error.message || error : null,
          error_code: error ? (error.statusCode || error.code) : null,
          ts:         new Date()
        }, _.isNil) },
        { upsert: true }
      );
    }

    let language = locale.split(/[-_]/)[0];

    // Overpass returns the list of the cities with an area covering
    // specified point. Pick the first one (with the lowest OSM ID).
    //
    if (_.has(body, 'elements.0.tags')) {
      let name = body.elements[0].tags['name:' + language] ||
                 body.elements[0].tags['name:en'] ||
                 body.elements[0].tags.name;

      if (name) return { name };
    }

    return null;
  }

  // Calculate Great Circle distance between two coordinates,
  // see https://stackoverflow.com/questions/27928
  //
  function distance(lat1, lon1, lat2, lon2) {
    let deg2rad = Math.PI / 180;
    let a = 0.5 - Math.cos((lat2 - lat1) * deg2rad) / 2 +
            Math.cos(lat1 * deg2rad) * Math.cos(lat2 * deg2rad) *
            (1 - Math.cos((lon2 - lon1) * deg2rad)) / 2;

    return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
  }

  // Get location name using overpass `around` operator (fetches all cities
  // within 100km radius and returns the closest one).
  //
  async function resolve_location_overpass_nearest(lat, lon, locale) {
    let req_id = N.models.core.GeoOverpassNearest.hash([ lon, lat ]);

    let cached = await N.models.core.GeoOverpassNearest.findOne({ hash: req_id }).lean(true);
    let body;

    if (cached) {
      if (cached.error || !cached.result) return null;

      body = JSON.parse(cached.result);
    } else {
      let now = Date.now();

      // Limit to 1 request per second, this should keep us below 5GB/day limit
      // https://wiki.openstreetmap.org/wiki/Overpass_API
      //
      // In case it hits their limits too often, you can check free slots here:
      // http://overpass-api.de/api/status
      //
      if (Math.abs(now - overpass_last_request) < 1000) {
        await new Promise(resolve => setTimeout(resolve, 1000 - Math.abs(now - overpass_last_request)));
      }

      overpass_last_request = Date.now();

      let script = `
        [timeout:20][out:json];
        node[place="city"](around:100000,${lat},${lon});
        out;
      `.replace(/\s+/g, ' ');

      let response, error;

      try {
        response = await got('http://overpass-api.de/api/interpreter', {
          query:   { data: script },
          headers: { 'user-agent': userAgent },
          timeout: 30000,
          retry:   1
        });

        body = JSON.parse(response.body);
      } catch (err) {
        // keep 400 errors (syntax error in the request), re-throw the rest
        if (err.statusCode !== 400) throw err;

        error = err;
      }

      await N.models.core.GeoOverpassNearest.updateOne(
        { hash: req_id },
        { $set: _.omitBy({
          location:   [ lon, lat ],
          result:     error ? null : response && response.body,
          error:      error ? error.message || error : null,
          error_code: error ? (error.statusCode || error.code) : null,
          ts:         new Date()
        }, _.isNil) },
        { upsert: true }
      );
    }

    if (!body || !body.elements || !body.elements.length) return null;

    // manually add distance to each city
    body.elements.forEach(function (node) {
      if (typeof node.lat === 'number' && typeof node.lon === 'number') {
        node.distance = distance(lat, lon, node.lat, node.lon);
      } else {
        node.distance = Infinity;
      }
    });

    let elements = body.elements.sort((a, b) => (a.distance > b.distance ? 1 : -1));

    let language = locale.split(/[-_]/)[0];

    // Overpass returns the list of the cities nearby,
    // we get one with lowest distance.
    //
    if (elements[0] && elements[0].tags) {
      let name = elements[0].tags['name:' + language] ||
                 elements[0].tags['name:en'] ||
                 elements[0].tags.name;

      if (name) {
        return { name: '~' + name, distance: elements[0].distance };
      }
    }

    return null;
  }

  // Get location name using nominatim (fetches an address, gets city from it;
  // city is determined using heuristics and sometimes incorrectly for large
  // cities where district is defined at the same admin level as city itself).
  //
  async function resolve_location_nominatim(lat, lon, locale) {
    let req_id = N.models.core.GeoNominatim.hash([ lon, lat ], locale);

    let cached = await N.models.core.GeoNominatim.findOne({ hash: req_id }).lean(true);
    let body;

    if (cached) {
      if (cached.error || !cached.result) return null;

      body = JSON.parse(cached.result);
    } else {
      let now = Date.now();

      // limit to 1 request per second according to
      // http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy
      if (Math.abs(now - nominatim_last_request) < 1000) {
        await new Promise(resolve => setTimeout(resolve, 1000 - Math.abs(now - nominatim_last_request)));
      }

      nominatim_last_request = Date.now();

      // accept-language field with fallback, e.g. `ru,en;q=0.8`,
      let language = locale.split(/[-_]/)[0];

      if (language !== 'en') language += ',en;q=0.8';

      let response = await got('http://nominatim.openstreetmap.org/reverse', {
        query: {
          format: 'json',
          'accept-language': language,
          lat,
          lon,
          zoom: 15
        },
        headers: { 'user-agent': userAgent },
        timeout: 15000,
        retry:   1
      });

      body = JSON.parse(response.body);

      let error = new Error(body.error);

      await N.models.core.GeoNominatim.updateOne(
        { hash: req_id },
        { $set: _.omitBy({
          location:   [ lon, lat ],
          result:     error ? null : response && response.body,
          error:      error ? error.message : null,
          ts:         new Date(),
          locale
        }, _.isNil) },
        { upsert: true }
      );
    }

    if (body && body.address) {
      let name = body.address.city ||
                 body.address.town ||
                 body.address.village ||
                 body.address.hamlet;

      if (name) return { name };
    }

    return null;
  }

  N.wire.on('init:jobs', function register_geo_location_resolve() {
    N.queue.registerTask({
      name: 'geo_location_resolve',

      // static id to ensure that it's singleton
      taskID: () => 'geo_location_resolve',

      async process() {
        const extendDeadline = _.throttle(() => this.setDeadline(120000), 10000);

        let last_request_time = 0;

        for (;;) {
          extendDeadline();

          let queue_name = 'geo:location:fast';

          // params has the form of `lon:lat:locale`,
          // e.g. `0.12:51.5:en-US` for 51.5°N 0.12°W
          let params = await N.redis.lpopAsync(queue_name);

          if (!params) {
            queue_name = 'geo:location';
            params = await N.redis.lpopAsync(queue_name);
          }

          if (!params) {
            // Wait for new data for 2 more seconds after queue empties,
            // then stop the task.
            //
            // Delay is necessary to avoid multiple tasks/processes messing
            // with rate limit counters.
            //
            if (Math.abs(Date.now() - last_request_time) > 2000) {
              break;
            } else {
              await new Promise(resolve => setTimeout(resolve, 2000));
              continue;
            }
          }

          let [ lon, lat, locale ] = params.split(':', 3);

          let hash = N.models.core.Location.hash([ lon, lat ], locale);

          if (await N.models.core.Location.findOne({ hash }).lean(true)) {
            // location is already resolved
            continue;
          }

          let result;
          let provider_name;
          let providers = {
            overpass: resolve_location_overpass,
            nominatim: resolve_location_nominatim,
            overpass_nearest: resolve_location_overpass_nearest
          };

          try {
            /* eslint-disable max-depth */
            for (provider_name of Object.keys(providers)) {
              result = await providers[provider_name](lat, lon, locale);

              if (result) break;
            }
          } catch (err) {
            // in case of errors, put coordinates back into queue and
            // pause this task for 10 minutes
            N.logger.error(`Geocoding service error, provider=${provider_name}` +
                           `, lon=${lon}, lat=${lat}: ${err.message || err}`);
            await N.redis.rpushAsync(queue_name, params);
            this.setDeadline(12 * 60 * 1000);
            await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000));
            continue;
          }

          await N.models.core.Location.updateOne(
            { hash: N.models.core.Location.hash([ lon, lat ], locale) },
            { $set: _.omitBy({
              location: [ lon, lat ],
              provider: result ? result.provider : null,
              name:     result ? result.name : null,
              distance: result ? result.distance : null,
              ts:       new Date(),
              locale
            }, _.isNil) },
            { upsert: true }
          );

          last_request_time = Date.now();
        }
      }
    });
  });
};
<commit_msg>Update got api to v10<commit_after>// Find location name
//
// In:
//  - redis set `geo:location` (`lon:lat:locale`)
//  - redis set `geo:location:fast` (`lon:lat:locale`)
//

'use strict';

const _       = require('lodash');
const got     = require('got');
const got_pkg = require('got/package.json');

module.exports = function (N) {
  let overpass_last_request  = 0;
  let nominatim_last_request = 0;

  let rootUrl = _.get(N.config, 'bind.default.mount', 'http://localhost') + '/';
  let userAgent = `${got_pkg.name}/${got_pkg.version} (Nodeca; +${rootUrl})`;

  // Get location name using overpass `is_in` operator (fetches a place
  // defined as an area that cover specified location).
  //
  async function resolve_location_overpass(lat, lon, locale) {
    let req_id = N.models.core.GeoOverpass.hash([ lon, lat ]);

    let cached = await N.models.core.GeoOverpass.findOne({ hash: req_id }).lean(true);
    let body;

    if (cached) {
      if (cached.error || !cached.result) return null;

      body = JSON.parse(cached.result);
    } else {
      let now = Date.now();

      // Limit to 1 request per second, this should keep us below 5GB/day limit
      // https://wiki.openstreetmap.org/wiki/Overpass_API
      //
      // In case it hits their limits too often, you can check free slots here:
      // http://overpass-api.de/api/status
      //
      if (Math.abs(now - overpass_last_request) < 1000) {
        await new Promise(resolve => setTimeout(resolve, 1000 - Math.abs(now - overpass_last_request)));
      }

      overpass_last_request = Date.now();

      let script = `
        [timeout:5][out:json];
        is_in(${lat},${lon})->.a;
        area.a[place~"city|town|village|hamlet"];
        out 1;
      `.replace(/\s+/g, ' ');

      let response, error;

      try {
        response = await got('http://overpass-api.de/api/interpreter', {
          searchParams: { data: script },
          headers:      { 'user-agent': userAgent },
          timeout:      15000,
          retry:        1
        });

        body = JSON.parse(response.body);
      } catch (err) {
        // keep 400 errors (syntax error in the request), re-throw the rest
        if ((err.response || {}).statusCode !== 400) throw err;

        error = err;
      }

      await N.models.core.GeoOverpass.updateOne(
        { hash: req_id },
        { $set: _.omitBy({
          location:   [ lon, lat ],
          result:     error ? null : response && response.body,
          error:      error ? error.message || error : null,
          error_code: error ? ((error.response || {}).statusCode || error.code) : null,
          ts:         new Date()
        }, _.isNil) },
        { upsert: true }
      );
    }

    let language = locale.split(/[-_]/)[0];

    // Overpass returns the list of the cities with an area covering
    // specified point. Pick the first one (with the lowest OSM ID).
    //
    if (_.has(body, 'elements.0.tags')) {
      let name = body.elements[0].tags['name:' + language] ||
                 body.elements[0].tags['name:en'] ||
                 body.elements[0].tags.name;

      if (name) return { name };
    }

    return null;
  }

  // Calculate Great Circle distance between two coordinates,
  // see https://stackoverflow.com/questions/27928
  //
  function distance(lat1, lon1, lat2, lon2) {
    let deg2rad = Math.PI / 180;
    let a = 0.5 - Math.cos((lat2 - lat1) * deg2rad) / 2 +
            Math.cos(lat1 * deg2rad) * Math.cos(lat2 * deg2rad) *
            (1 - Math.cos((lon2 - lon1) * deg2rad)) / 2;

    return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
  }

  // Get location name using overpass `around` operator (fetches all cities
  // within 100km radius and returns the closest one).
  //
  async function resolve_location_overpass_nearest(lat, lon, locale) {
    let req_id = N.models.core.GeoOverpassNearest.hash([ lon, lat ]);

    let cached = await N.models.core.GeoOverpassNearest.findOne({ hash: req_id }).lean(true);
    let body;

    if (cached) {
      if (cached.error || !cached.result) return null;

      body = JSON.parse(cached.result);
    } else {
      let now = Date.now();

      // Limit to 1 request per second, this should keep us below 5GB/day limit
      // https://wiki.openstreetmap.org/wiki/Overpass_API
      //
      // In case it hits their limits too often, you can check free slots here:
      // http://overpass-api.de/api/status
      //
      if (Math.abs(now - overpass_last_request) < 1000) {
        await new Promise(resolve => setTimeout(resolve, 1000 - Math.abs(now - overpass_last_request)));
      }

      overpass_last_request = Date.now();

      let script = `
        [timeout:20][out:json];
        node[place="city"](around:100000,${lat},${lon});
        out;
      `.replace(/\s+/g, ' ');

      let response, error;

      try {
        response = await got('http://overpass-api.de/api/interpreter', {
          searchParams: { data: script },
          headers:      { 'user-agent': userAgent },
          timeout:      30000,
          retry:        1
        });

        body = JSON.parse(response.body);
      } catch (err) {
        // keep 400 errors (syntax error in the request), re-throw the rest
        if ((err.response || {}).statusCode !== 400) throw err;

        error = err;
      }

      await N.models.core.GeoOverpassNearest.updateOne(
        { hash: req_id },
        { $set: _.omitBy({
          location:   [ lon, lat ],
          result:     error ? null : response && response.body,
          error:      error ? error.message || error : null,
          error_code: error ? ((error.response || {}).statusCode || error.code) : null,
          ts:         new Date()
        }, _.isNil) },
        { upsert: true }
      );
    }

    if (!body || !body.elements || !body.elements.length) return null;

    // manually add distance to each city
    body.elements.forEach(function (node) {
      if (typeof node.lat === 'number' && typeof node.lon === 'number') {
        node.distance = distance(lat, lon, node.lat, node.lon);
      } else {
        node.distance = Infinity;
      }
    });

    let elements = body.elements.sort((a, b) => (a.distance > b.distance ? 1 : -1));

    let language = locale.split(/[-_]/)[0];

    // Overpass returns the list of the cities nearby,
    // we get one with lowest distance.
    //
    if (elements[0] && elements[0].tags) {
      let name = elements[0].tags['name:' + language] ||
                 elements[0].tags['name:en'] ||
                 elements[0].tags.name;

      if (name) {
        return { name: '~' + name, distance: elements[0].distance };
      }
    }

    return null;
  }

  // Get location name using nominatim (fetches an address, gets city from it;
  // city is determined using heuristics and sometimes incorrectly for large
  // cities where district is defined at the same admin level as city itself).
  //
  async function resolve_location_nominatim(lat, lon, locale) {
    let req_id = N.models.core.GeoNominatim.hash([ lon, lat ], locale);

    let cached = await N.models.core.GeoNominatim.findOne({ hash: req_id }).lean(true);
    let body;

    if (cached) {
      if (cached.error || !cached.result) return null;

      body = JSON.parse(cached.result);
    } else {
      let now = Date.now();

      // limit to 1 request per second according to
      // http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy
      if (Math.abs(now - nominatim_last_request) < 1000) {
        await new Promise(resolve => setTimeout(resolve, 1000 - Math.abs(now - nominatim_last_request)));
      }

      nominatim_last_request = Date.now();

      // accept-language field with fallback, e.g. `ru,en;q=0.8`,
      let language = locale.split(/[-_]/)[0];

      if (language !== 'en') language += ',en;q=0.8';

      let response = await got('http://nominatim.openstreetmap.org/reverse', {
        searchParams: {
          format: 'json',
          'accept-language': language,
          lat,
          lon,
          zoom: 15
        },
        headers: { 'user-agent': userAgent },
        timeout: 15000,
        retry:   1
      });

      body = JSON.parse(response.body);

      let error = new Error(body.error);

      await N.models.core.GeoNominatim.updateOne(
        { hash: req_id },
        { $set: _.omitBy({
          location:   [ lon, lat ],
          result:     error ? null : response && response.body,
          error:      error ? error.message : null,
          ts:         new Date(),
          locale
        }, _.isNil) },
        { upsert: true }
      );
    }

    if (body && body.address) {
      let name = body.address.city ||
                 body.address.town ||
                 body.address.village ||
                 body.address.hamlet;

      if (name) return { name };
    }

    return null;
  }

  N.wire.on('init:jobs', function register_geo_location_resolve() {
    N.queue.registerTask({
      name: 'geo_location_resolve',

      // static id to ensure that it's singleton
      taskID: () => 'geo_location_resolve',

      async process() {
        const extendDeadline = _.throttle(() => this.setDeadline(120000), 10000);

        let last_request_time = 0;

        for (;;) {
          extendDeadline();

          let queue_name = 'geo:location:fast';

          // params has the form of `lon:lat:locale`,
          // e.g. `0.12:51.5:en-US` for 51.5°N 0.12°W
          let params = await N.redis.lpopAsync(queue_name);

          if (!params) {
            queue_name = 'geo:location';
            params = await N.redis.lpopAsync(queue_name);
          }

          if (!params) {
            // Wait for new data for 2 more seconds after queue empties,
            // then stop the task.
            //
            // Delay is necessary to avoid multiple tasks/processes messing
            // with rate limit counters.
            //
            if (Math.abs(Date.now() - last_request_time) > 2000) {
              break;
            } else {
              await new Promise(resolve => setTimeout(resolve, 2000));
              continue;
            }
          }

          let [ lon, lat, locale ] = params.split(':', 3);

          let hash = N.models.core.Location.hash([ lon, lat ], locale);

          if (await N.models.core.Location.findOne({ hash }).lean(true)) {
            // location is already resolved
            continue;
          }

          let result;
          let provider_name;
          let providers = {
            overpass: resolve_location_overpass,
            nominatim: resolve_location_nominatim,
            overpass_nearest: resolve_location_overpass_nearest
          };

          try {
            /* eslint-disable max-depth */
            for (provider_name of Object.keys(providers)) {
              result = await providers[provider_name](lat, lon, locale);

              if (result) break;
            }
          } catch (err) {
            // in case of errors, put coordinates back into queue and
            // pause this task for 10 minutes
            N.logger.error(`Geocoding service error, provider=${provider_name}` +
                           `, lon=${lon}, lat=${lat}: ${err.message || err}`);
            await N.redis.rpushAsync(queue_name, params);
            this.setDeadline(12 * 60 * 1000);
            await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000));
            continue;
          }

          await N.models.core.Location.updateOne(
            { hash: N.models.core.Location.hash([ lon, lat ], locale) },
            { $set: _.omitBy({
              location: [ lon, lat ],
              provider: result ? result.provider : null,
              name:     result ? result.name : null,
              distance: result ? result.distance : null,
              ts:       new Date(),
              locale
            }, _.isNil) },
            { upsert: true }
          );

          last_request_time = Date.now();
        }
      }
    });
  });
};
<|endoftext|>


<commit_before>var path = require('path');
var webpack = require('webpack');

/*
  This function will be called with the config name (dashboard_specs, builder_spec) so
  you have a change to customize webpack for each bundle.
*/
module.exports = {
  task: function (config) {
    return {
      mode: 'development',
      entry: {
        main: [
          // To be filled by grunt
        ]
      },
      output: {
        path: path.resolve(path.resolve('.'), '.grunt', config),
        filename: '[name].affected-specs.js'
      },
      resolve: {
        symlinks: false,
        modules: require('../../../../webpack/common/modules.js'),
        alias: require('../../../../webpack/common/alias')
      },
      module: {
        rules: [
          {
            test: /\.js$/,
            loader: 'shim-loader',
            query: {
              shim: {
                'html-css-sanitizer': {
                  exports: 'html'
                }
              }
            }
          },
          {
            test: /\.js$/,
            loader: 'babel-loader',
            include: [
              path.resolve(path.resolve('.'), 'lib/assets/javascripts/carto-node'),
              path.resolve(path.resolve('.'), 'lib/assets/test/spec/carto-node'),
              path.resolve(path.resolve('.'), 'lib/assets/javascripts/dashboard'),
              path.resolve(path.resolve('.'), 'lib/assets/test/spec/dashboard'),
              path.resolve(path.resolve('.'), 'lib/assets/test/spec/fixtures'),
              path.resolve(path.resolve('.'), 'lib/assets/javascripts/dashboard')
            ],
            options: {
              presets: [
                ['es2015', { 'modules': false }]
              ],
              plugins: ['transform-object-assign', 'transform-object-rest-spread']
            }
          },
          {
            test: /\.tpl$/,
            use: 'tpl-loader'
          },
          {
            test: /\.mustache$/,
            use: 'raw-loader'
          }
        ],
        exprContextRegExp: /$^/,
        exprContextCritical: false
      },
      optimization: {
        splitChunks: {
          cacheGroups: {
            vendor: {
              name: 'vendor',
              chunks: 'all',
              test: /[\\/]node_modules[\\/]/
            }
          }
        }
      },
      plugins: [
        new webpack.ProvidePlugin({
          $: 'jquery',
          jQuery: 'jquery',
          L: 'leaflet',
          'window.jQuery': 'jquery'
        }),
        new webpack.SourceMapDevToolPlugin({
          filename: '[file].map',
          exclude: /vendor/
        }),
        new webpack.DefinePlugin({
          __ENV__: JSON.stringify('test')
        })
      ],
      target: 'web',
      node: {
        fs: 'empty'
      }
    };
  }
};
<commit_msg>Allow ES6 in internal-carto.js for the tests<commit_after>var path = require('path');
var webpack = require('webpack');

/*
  This function will be called with the config name (dashboard_specs, builder_spec) so
  you have a change to customize webpack for each bundle.
*/
module.exports = {
  task: function (config) {
    return {
      mode: 'development',
      entry: {
        main: [
          // To be filled by grunt
        ]
      },
      output: {
        path: path.resolve(path.resolve('.'), '.grunt', config),
        filename: '[name].affected-specs.js'
      },
      resolve: {
        symlinks: false,
        modules: require('../../../../webpack/common/modules.js'),
        alias: require('../../../../webpack/common/alias')
      },
      module: {
        rules: [
          {
            test: /\.js$/,
            loader: 'shim-loader',
            query: {
              shim: {
                'html-css-sanitizer': {
                  exports: 'html'
                }
              }
            }
          },
          {
            test: /\.js$/,
            loader: 'babel-loader',
            include: [
              path.resolve(path.resolve('.'), 'lib/assets/javascripts/carto-node'),
              path.resolve(path.resolve('.'), 'lib/assets/test/spec/carto-node'),
              path.resolve(path.resolve('.'), 'lib/assets/javascripts/dashboard'),
              path.resolve(path.resolve('.'), 'lib/assets/test/spec/dashboard'),
              path.resolve(path.resolve('.'), 'lib/assets/test/spec/fixtures'),
              path.resolve(path.resolve('.'), 'node_modules/internal-carto.js')
            ],
            options: {
              presets: [
                ['es2015', { 'modules': false }]
              ],
              plugins: ['transform-object-assign', 'transform-object-rest-spread']
            }
          },
          {
            test: /\.tpl$/,
            use: 'tpl-loader'
          },
          {
            test: /\.mustache$/,
            use: 'raw-loader'
          }
        ],
        exprContextRegExp: /$^/,
        exprContextCritical: false
      },
      optimization: {
        splitChunks: {
          cacheGroups: {
            vendor: {
              name: 'vendor',
              chunks: 'all',
              test: /[\\/]node_modules[\\/]/
            }
          }
        }
      },
      plugins: [
        new webpack.ProvidePlugin({
          $: 'jquery',
          jQuery: 'jquery',
          L: 'leaflet',
          'window.jQuery': 'jquery'
        }),
        new webpack.SourceMapDevToolPlugin({
          filename: '[file].map',
          exclude: /vendor/
        }),
        new webpack.DefinePlugin({
          __ENV__: JSON.stringify('test')
        })
      ],
      target: 'web',
      node: {
        fs: 'empty'
      }
    };
  }
};
<|endoftext|>


<commit_before>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You 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.
 */

var sys = require('sys');
var path = require('path');
var http = require('util/http');

var sprintf = require('extern/sprintf').sprintf;

var config = {
  'short_description': 'Print log file for a specified service',
  'long_description': 'Print a log file for a specified service. ' +
                      'If a --follow switch is provided, new data is ' +
                      'printed as soon as it is available.',
  'required_arguments' : [['name', 'Service name']],
  'optional_arguments': [['bytes', 'How many bytes from the end of the log file to print']],
  'options': [
    {
      names: ['--follow', '-F'],
      dest: 'follow',
      action: 'store_true',
      desc: 'Output new data as some as it is available (aka tail -F)'
    }
  ],
  'uses_global_options': ['remote']
};

function handle_command(args) {
  var service_name, remote_path, bytes;

  service_name = args.name;

  if (args.bytes) {
    bytes = args.bytes;
  }
  else {
    // Return 500 bytes from the end of the log file by default
    bytes = 500;
  }

  if (args.follow) {
    remote_path = path.join('/services/', service_name, '/tail/', bytes, '/follow/');
  }
  else {
    remote_path = path.join('/services/', service_name, '/tail/', bytes, '/');
  }

  service_name = args.name;

  http.client(args.remote, function(client) {
    var request = client.request('GET', remote_path, {
      'host': 'localhost'
    });

    request.on('response', function(response) {
      response.setEncoding('ascii');

      response.on('data', function(chunk) {
        if (response.statusCode !== 200) {
          var error = JSON.parse(chunk);
          sys.puts(sprintf('Error: %s', error.message));

          return;
        }

        sys.print(chunk);
      });
    });

    // Send the request
    request.end();
  });
}

exports.config = config;
exports.handle_command = handle_command;
<commit_msg>Error handling cleanup in 'services tail'<commit_after>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You 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.
 */

var sys = require('sys');
var path = require('path');
var http = require('util/http');

var sprintf = require('extern/sprintf').sprintf;

var config = {
  'short_description': 'Print log file for a specified service',
  'long_description': 'Print a log file for a specified service. ' +
                      'If a --follow switch is provided, new data is ' +
                      'printed as soon as it is available.',
  'required_arguments' : [['name', 'Service name']],
  'optional_arguments': [['bytes', 'How many bytes from the end of the log file to print']],
  'options': [
    {
      names: ['--follow', '-F'],
      dest: 'follow',
      action: 'store_true',
      desc: 'Output new data as some as it is available (aka tail -F)'
    }
  ],
  'uses_global_options': ['remote']
};

function handle_command(args) {
  var service_name, remote_path, bytes;

  service_name = args.name;

  if (args.bytes) {
    bytes = args.bytes;
  }
  else {
    // Return 500 bytes from the end of the log file by default
    bytes = 500;
  }

  if (args.follow) {
    remote_path = path.join('/services/', service_name, '/tail/', bytes, '/follow/');
  }
  else {
    remote_path = path.join('/services/', service_name, '/tail/', bytes, '/');
  }

  service_name = args.name;

  http.client(args.remote, function(err, client) {
    if (err) {
      sys.puts(sprintf('Error: %s', err.message));
      return;
    }

    client.on('error', function(err) {
      sys.puts(sprintf('Error: %s', err.message));
    });

    var request = client.request('GET', remote_path, {
      'host': 'localhost'
    });

    function on_data(chunk) {
      sys.print(chunk);
    }

    request.on('response', function(response) {
      if (response.statusCode !== 200) {
        var body = '';
        response.on('data', function(chunk) {
          body += chunk;
        });
        response.on('end', function() {
          try {
            var error = JSON.parse(body);
            sys.puts(sprintf('Error: %s', error.message));
          }
          catch (e) {
            sys.puts('Error: invalid response');
          }
        });
      }
      else {
        response.setEncoding('ascii');
        response.on('data', on_data);
      }
    });

    // Send the request
    request.end();
  });
}

exports.config = config;
exports.handle_command = handle_command;
<|endoftext|>


<commit_before>(function() {

var languages = [
    'bg', 'ca', 'cs', 'de', 'el', 'en-US', 'es', 'eu', 'fr', 'ga-IE', 'hr',
    'hu', 'it', 'ja', 'mk', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sr',
    'sr-Latn', 'tr', 'zh-TW', 'dbg'
];
var body_langs;
if (body_langs = document.body.getAttribute('data-languages')) {
    languages = JSON.parse(body_langs);
}

var lang_expander = {
    'en': 'en-US', 'ga': 'ga-IE',
    'pt': 'pt-BR', 'sv': 'sv-SE',
    'zh': 'zh-CN', 'sr': 'sr-Latn'
};

function get_locale(locale) {
    if (languages.indexOf(locale) !== -1) {
        return locale;
    }
    locale = locale.split('-')[0];
    if (languages.indexOf(locale) !== -1) {
        return locale;
    }
    if (locale in lang_expander) {
        locale = lang_expander[locale];
        if (languages.indexOf(locale) !== -1) {
            return locale;
        }
    }
    return 'en-US';
}

if (!window.define) {
    var qs_lang = /[\?&]lang=([\w\-]+)/i.exec(window.location.search);
    var locale = get_locale((qs_lang && qs_lang[1]) || navigator.language || navigator.userLanguage);
    if (locale === 'en-US') {
        window.navigator.l10n = {language: 'en-US'};
        return;
    }

    // Cachebust the .js file for our CDN.
    var build_id = document.body.getAttribute('data-buildIdJs') || +new Date();
    var repo = document.body.getAttribute('data-repo');
    document.write('<script src="/media/' + (repo ? repo + '/' : '') + 'locales/' + locale + '.js?b=' + build_id + '"></script>');

} else {
    define('l10n', ['format'], function(format) {
        var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];

        function get(str, args, context) {
            context = context || navigator;
            var out;
            if (context.l10n && context.l10n.strings && str in context.l10n.strings) {
                out = context.l10n.strings[str].body || str;
            } else {
                out = str;
            }
            if (args) {
                out = format.format(out, args);
            }
            return out;
        }
        function nget(str, plural, args, context) {
            context = context || navigator;
            if (!args || !('n' in args)) {
                throw new Error('`n` not passed to ngettext');
            }
            var out;
            var n = args.n;
            var strings;
            var fallback = n === 1 ? str : plural;
            if (context.l10n && context.l10n.strings && str in (strings = context.l10n.strings)) {
                if (strings[str].plurals) {
                    // +true is 1 / +false is 0
                    var plid = +context.l10n.pluralize(n);
                    out = strings[str].plurals[plid] || fallback;
                } else {
                    // Support for languages like zh-TW where there is no plural form.
                    out = strings[str].body || fallback;
                }
            } else {
                out = fallback;
            }
            return format.format(out, args);
        }

        window.gettext = get;
        window.ngettext = nget;

        return {
            gettext: get,
            ngettext: nget,
            getDirection: function(context) {
                var language = context ? context.language : window.navigator.l10n.language;
                if (language.indexOf('-') > -1) {
                    language = language.split('-')[0];
                }
                // http://www.w3.org/International/questions/qa-scripts
                // Arabic, Hebrew, Farsi, Pashto, Urdu
                return rtlList.indexOf(language) >= 0 ? 'rtl' : 'ltr';
            },
            getLocale: get_locale,
            languages: languages
        };
    });
}
})();
<commit_msg>lol make other locales load (bug 948663)<commit_after>(function() {

var languages = [
    // 'bg', 'ca', 'cs', 'de', 'el', 'en-US', 'es', 'eu', 'fr', 'ga-IE', 'hr',
    // 'hu', 'it', 'ja', 'mk', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sr',
    // 'sr-Latn', 'tr', 'zh-TW', 'dbg'

    // See bug 948663.
    'en-US'
];
var body_langs;
if (body_langs = document.body.getAttribute('data-languages')) {
    languages = JSON.parse(body_langs);
}

var lang_expander = {
    'en': 'en-US', 'ga': 'ga-IE',
    'pt': 'pt-BR', 'sv': 'sv-SE',
    'zh': 'zh-CN', 'sr': 'sr-Latn'
};

function get_locale(locale) {
    if (languages.indexOf(locale) !== -1) {
        return locale;
    }
    locale = locale.split('-')[0];
    if (languages.indexOf(locale) !== -1) {
        return locale;
    }
    if (locale in lang_expander) {
        locale = lang_expander[locale];
        if (languages.indexOf(locale) !== -1) {
            return locale;
        }
    }
    return 'en-US';
}

if (!window.define) {
    var qs_lang = /[\?&]lang=([\w\-]+)/i.exec(window.location.search);
    var locale = get_locale((qs_lang && qs_lang[1]) || navigator.language || navigator.userLanguage);
    if (locale === 'en-US') {
        window.navigator.l10n = {language: 'en-US'};
        return;
    }

    // Cachebust the .js file for our CDN.
    var build_id = document.body.getAttribute('data-buildIdJs') || +new Date();
    var repo = document.body.getAttribute('data-repo');
    document.write('<script src="/media/' + (repo ? repo + '/' : '') + 'locales/' + locale + '.js?b=' + build_id + '"></script>');

} else {
    define('l10n', ['format'], function(format) {
        var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];

        function get(str, args, context) {
            context = context || navigator;
            var out;
            if (context.l10n && context.l10n.strings && str in context.l10n.strings) {
                out = context.l10n.strings[str].body || str;
            } else {
                out = str;
            }
            if (args) {
                out = format.format(out, args);
            }
            return out;
        }
        function nget(str, plural, args, context) {
            context = context || navigator;
            if (!args || !('n' in args)) {
                throw new Error('`n` not passed to ngettext');
            }
            var out;
            var n = args.n;
            var strings;
            var fallback = n === 1 ? str : plural;
            if (context.l10n && context.l10n.strings && str in (strings = context.l10n.strings)) {
                if (strings[str].plurals) {
                    // +true is 1 / +false is 0
                    var plid = +context.l10n.pluralize(n);
                    out = strings[str].plurals[plid] || fallback;
                } else {
                    // Support for languages like zh-TW where there is no plural form.
                    out = strings[str].body || fallback;
                }
            } else {
                out = fallback;
            }
            return format.format(out, args);
        }

        window.gettext = get;
        window.ngettext = nget;

        return {
            gettext: get,
            ngettext: nget,
            getDirection: function(context) {
                var language = context ? context.language : window.navigator.l10n.language;
                if (language.indexOf('-') > -1) {
                    language = language.split('-')[0];
                }
                // http://www.w3.org/International/questions/qa-scripts
                // Arabic, Hebrew, Farsi, Pashto, Urdu
                return rtlList.indexOf(language) >= 0 ? 'rtl' : 'ltr';
            },
            getLocale: get_locale,
            languages: languages
        };
    });
}
})();
<|endoftext|>


<commit_before><commit_msg>Hide highlights dialog on resize<commit_after><|endoftext|>