<commit_before>/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the Clear BSD license.
 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control.js
 */

/**
 * Class: OpenLayers.Control.CacheWrite
 * A control for caching image tiles in the browser's local storage. The
 * <OpenLayers.Control.CacheRead> control is used to fetch and use the cached
 * tile images.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, {

    /**
     * APIProperty: events
     * {<OpenLayers.Events>} Events instance for listeners and triggering
     *     control specific events.
     *
     * To register events in the constructor, configure <eventListeners>.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * control.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types (in addition to those from <OpenLayers.Control.events>):
     * cachefull - Triggered when the cache is full. Listeners receive an
     *     object with a tile property as first argument. The tile references
     *     the tile that couldn't be cached.
     */

    /**
     * APIProperty: eventListeners
     * {Object} Object with event listeners, keyed by event name. An optional
     *     scope property defines the scope that listeners will be executed in.
     */

    /**
     * APIProperty: layers
     * {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, caching
     *     will be enabled for these layers only, otherwise for all cacheable
     *     layers.
     */
    layers: null,

    /**
     * APIProperty: imageFormat
     * {String} The image format used for caching. The default is "image/png".
     *     Supported formats depend on the user agent. If an unsupported
     *     <imageFormat> is provided, "image/png" will be used. For aerial
     *     imagery, "image/jpeg" is recommended.
     */
    imageFormat: "image/png",

    /**
     * Property: quotaRegEx
     * {RegExp}
     */
    quotaRegEx: (/quota/i),

    /**
     * Constructor: OpenLayers.Control.CacheWrite
     *
     * Parameters:
     * options - {Object} Object with API properties for this control.
     */

    /**
     * Method: setMap
     * Set the map property for the control.
     *
     * Parameters:
     * map - {<OpenLayers.Map>}
     */
    setMap: function(map) {
        OpenLayers.Control.prototype.setMap.apply(this, arguments);
        var i, layers = this.layers || this.map.layers;
        for (i=layers.length-1; i>=0; --i) {
            this.addLayer({layer: layers[i]});
        }
        if (!this.layers) {
            this.map.events.on({
                addlayer: this.addLayer,
                removeLayer: this.removeLayer,
                scope: this
            });
        }
    },

    /**
     * Method: addLayer
     * Adds a layer to the control. Once added, tiles requested for this layer
     *     will be cached.
     *
     * Parameters:
     * evt - {Object} Object with a layer property referencing an
     *     <OpenLayers.Layer> instance
     */
    addLayer: function(evt) {
        evt.layer.events.register("tileloaded", this, this.cache);
    },

    /**
     * Method: removeLayer
     * Removes a layer from the control. Once removed, tiles requested for this
     *     layer will no longer be cached.
     *
     * Parameters:
     * evt - {Object} Object with a layer property referencing an
     *     <OpenLayers.Layer> instance
     */
    removeLayer: function(evt) {
        evt.layer.events.unregister("tileloaded", this, this.cache);
    },

    /**
     * Method: cache
     * Adds a tile to the cache. When the cache is full, the "cachefull" event
     * is triggered.
     *
     * Parameters:
     * obj - {Object} Object with a tile property, tile being the
     *     <OpenLayers.Tile.Image> with the data to add to the cache
     */
    cache: function(obj) {
        if (this.active && window.localStorage) {
            var tile = obj.tile;
            if (tile.url.substr(0, 5) !== 'data:') {
                try {
                    var canvasContext = tile.getCanvasContext();
                    if (canvasContext) {
                        window.localStorage.setItem(
                            "olCache_" + tile.url,
                            canvasContext.canvas.toDataURL(this.imageFormat)
                        );
                    }
                } catch(e) {
                    // local storage full or CORS violation
                    var reason = e.name || e.message;
                    if (reason && this.quotaRegEx.test(reason)) {
                        this.events.triggerEvent("cachefull", {tile: tile});
                    } else {
                        // throw exception in the next cycle
                        window.setTimeout(function() { throw(e); }, 0);
                    }
                }
            }
        }
    },

    /**
     * Method: destroy
     * The destroy method is used to perform any clean up before the control
     * is dereferenced.  Typically this is where event listeners are removed
     * to prevent memory leaks.
     */
    destroy: function() {
        if (this.layers || this.map) {
            var i, layers = this.layers || this.map.layers;
            for (i=layers.length-1; i>=0; --i) {
                this.removeLayer({layer: layers[i]});
            }
        }
        if (this.map) {
            this.map.events.un({
                addlayer: this.addLayer,
                removeLayer: this.removeLayer,
                scope: this
            });
        }
        OpenLayers.Control.prototype.destroy.apply(this, arguments);
    },

    CLASS_NAME: "OpenLayers.Control.CacheWrite"
});

/**
 * APIFunction: OpenLayers.Control.CacheWrite.clearCache
 * Clears all tiles cached with <OpenLayers.Control.CacheWrite> from the cache.
 */
OpenLayers.Control.CacheWrite.clearCache = function() {
    if (!window.localStorage) { return; }
    var i, key;
    for (i=window.localStorage.length-1; i>=0; --i) {
        key = window.localStorage.key(i);
        if (key.substr(0, 8) === "olCache_") {
            window.localStorage.removeItem(key);
        }
    }
};
<commit_msg>Note about terms of service.<commit_after>/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the Clear BSD license.
 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Control.js
 */

/**
 * Class: OpenLayers.Control.CacheWrite
 * A control for caching image tiles in the browser's local storage. The
 * <OpenLayers.Control.CacheRead> control is used to fetch and use the cached
 * tile images.
 *
 * Note: Before using this control on any layer that is not your own, make sure
 * that the terms of service of the tile provider allow local storage of tiles.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, {

    /**
     * APIProperty: events
     * {<OpenLayers.Events>} Events instance for listeners and triggering
     *     control specific events.
     *
     * To register events in the constructor, configure <eventListeners>.
     *
     * Register a listener for a particular event with the following syntax:
     * (code)
     * control.events.register(type, obj, listener);
     * (end)
     *
     * Supported event types (in addition to those from <OpenLayers.Control.events>):
     * cachefull - Triggered when the cache is full. Listeners receive an
     *     object with a tile property as first argument. The tile references
     *     the tile that couldn't be cached.
     */

    /**
     * APIProperty: eventListeners
     * {Object} Object with event listeners, keyed by event name. An optional
     *     scope property defines the scope that listeners will be executed in.
     */

    /**
     * APIProperty: layers
     * {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, caching
     *     will be enabled for these layers only, otherwise for all cacheable
     *     layers.
     */
    layers: null,

    /**
     * APIProperty: imageFormat
     * {String} The image format used for caching. The default is "image/png".
     *     Supported formats depend on the user agent. If an unsupported
     *     <imageFormat> is provided, "image/png" will be used. For aerial
     *     imagery, "image/jpeg" is recommended.
     */
    imageFormat: "image/png",

    /**
     * Property: quotaRegEx
     * {RegExp}
     */
    quotaRegEx: (/quota/i),

    /**
     * Constructor: OpenLayers.Control.CacheWrite
     *
     * Parameters:
     * options - {Object} Object with API properties for this control.
     */

    /**
     * Method: setMap
     * Set the map property for the control.
     *
     * Parameters:
     * map - {<OpenLayers.Map>}
     */
    setMap: function(map) {
        OpenLayers.Control.prototype.setMap.apply(this, arguments);
        var i, layers = this.layers || this.map.layers;
        for (i=layers.length-1; i>=0; --i) {
            this.addLayer({layer: layers[i]});
        }
        if (!this.layers) {
            this.map.events.on({
                addlayer: this.addLayer,
                removeLayer: this.removeLayer,
                scope: this
            });
        }
    },

    /**
     * Method: addLayer
     * Adds a layer to the control. Once added, tiles requested for this layer
     *     will be cached.
     *
     * Parameters:
     * evt - {Object} Object with a layer property referencing an
     *     <OpenLayers.Layer> instance
     */
    addLayer: function(evt) {
        evt.layer.events.register("tileloaded", this, this.cache);
    },

    /**
     * Method: removeLayer
     * Removes a layer from the control. Once removed, tiles requested for this
     *     layer will no longer be cached.
     *
     * Parameters:
     * evt - {Object} Object with a layer property referencing an
     *     <OpenLayers.Layer> instance
     */
    removeLayer: function(evt) {
        evt.layer.events.unregister("tileloaded", this, this.cache);
    },

    /**
     * Method: cache
     * Adds a tile to the cache. When the cache is full, the "cachefull" event
     * is triggered.
     *
     * Parameters:
     * obj - {Object} Object with a tile property, tile being the
     *     <OpenLayers.Tile.Image> with the data to add to the cache
     */
    cache: function(obj) {
        if (this.active && window.localStorage) {
            var tile = obj.tile;
            if (tile.url.substr(0, 5) !== 'data:') {
                try {
                    var canvasContext = tile.getCanvasContext();
                    if (canvasContext) {
                        window.localStorage.setItem(
                            "olCache_" + tile.url,
                            canvasContext.canvas.toDataURL(this.imageFormat)
                        );
                    }
                } catch(e) {
                    // local storage full or CORS violation
                    var reason = e.name || e.message;
                    if (reason && this.quotaRegEx.test(reason)) {
                        this.events.triggerEvent("cachefull", {tile: tile});
                    } else {
                        // throw exception in the next cycle
                        window.setTimeout(function() { throw(e); }, 0);
                    }
                }
            }
        }
    },

    /**
     * Method: destroy
     * The destroy method is used to perform any clean up before the control
     * is dereferenced.  Typically this is where event listeners are removed
     * to prevent memory leaks.
     */
    destroy: function() {
        if (this.layers || this.map) {
            var i, layers = this.layers || this.map.layers;
            for (i=layers.length-1; i>=0; --i) {
                this.removeLayer({layer: layers[i]});
            }
        }
        if (this.map) {
            this.map.events.un({
                addlayer: this.addLayer,
                removeLayer: this.removeLayer,
                scope: this
            });
        }
        OpenLayers.Control.prototype.destroy.apply(this, arguments);
    },

    CLASS_NAME: "OpenLayers.Control.CacheWrite"
});

/**
 * APIFunction: OpenLayers.Control.CacheWrite.clearCache
 * Clears all tiles cached with <OpenLayers.Control.CacheWrite> from the cache.
 */
OpenLayers.Control.CacheWrite.clearCache = function() {
    if (!window.localStorage) { return; }
    var i, key;
    for (i=window.localStorage.length-1; i>=0; --i) {
        key = window.localStorage.key(i);
        if (key.substr(0, 8) === "olCache_") {
            window.localStorage.removeItem(key);
        }
    }
};
<|endoftext|>


<commit_before>var L = require("leaflet");
var EegeoMapController = require("./private/eegeo_map_controller");
var EmscriptenApi = require("./private/emscripten_api/emscripten_api");
var marker = require("./public/marker.js");
var popup = require("./public/popup.js");
var polygon = require("./public/polygon.js");
require("./private/polyfills.js");

var _baseUrl = "https://cdn-webgl.wrld3d.com/wrldjs/early_access/latest/";
var _appName = "eeGeoWebGL.jgz";

var _mapObjects = [];
var _emscriptenInitialized = false;

var createEmscriptenModule = function() {
	var Module = window.Module || {};
	window.Module = Module;
	Module["locateFile"] = function(url) {
		var absUrl = _baseUrl + url;
		return absUrl;
	};
	Module["onExit"] = function(exitCode) {
		if (exitCode === 1) {
			var message = "Error: eegeo.js failed to initialize";
			if (!Module.ctx) {
				message = "Error: WebGL unavailable in this browser";
			}
			_mapObjects.forEach(function(map) {
				map.onError(message);
			});
		}
	};
};

var initializeEmscripten = function() {
	if (!_emscriptenInitialized) {
		var script = document.createElement("script");
		script.src = _baseUrl + _appName;
		document.body.appendChild(script);
		_emscriptenInitialized = true;
	}
};

var findMapContainerElement = function(elementOrId) {
	var element = elementOrId;
	var id = null;
	if (typeof elementOrId === "string") {
		id = elementOrId;
		element = document.getElementById(id);
	}
	if (!element) {
		var idError = (id === null) ? "" : (" with id '" + id + "'");
		throw "No map container found" + idError;
	}
	return element;
};

var eeGeo = {
	map: function(domElement, apiKey, options) {

		createEmscriptenModule();

		domElement = findMapContainerElement(domElement);

		var browserDocument = document;
		var browserWindow = window;
		var module = browserWindow.Module;
		var mapId = _mapObjects.length;
		var mapApiObject = new EmscriptenApi(module);
		var mapOptions = options || {};
		var map = new EegeoMapController(mapId, mapApiObject, domElement, apiKey, browserWindow, browserDocument, module, mapOptions);
		_mapObjects.push(map);

		initializeEmscripten();

		return map.leafletMap;
	},

	Marker: marker.Marker,
	marker: marker.marker,
	Popup: popup.Popup,
	popup: popup.popup,
	Polygon: polygon.Polygon,
	polygon: polygon.polygon,

	indoors: require("./public/indoors/indoors"),
	space: require("./public/space"),
	themes: require("./public/themes"),

	getMapById: function(mapId) {
		return _mapObjects[mapId];
	}
};

window.L = L;
L.eeGeo = eeGeo;

// The default image path is broken when using Browserify - it searches the script tags on the page
L.Icon.Default.imagePath = "http://cdn.leafletjs.com/leaflet/v1.0.1/images/";

module.exports = L.eeGeo;
<commit_msg>Updated error message with updated package name. Buddy: Ben<commit_after>var L = require("leaflet");
var EegeoMapController = require("./private/eegeo_map_controller");
var EmscriptenApi = require("./private/emscripten_api/emscripten_api");
var marker = require("./public/marker.js");
var popup = require("./public/popup.js");
var polygon = require("./public/polygon.js");
require("./private/polyfills.js");

var _baseUrl = "https://cdn-webgl.wrld3d.com/wrldjs/early_access/latest/";
var _appName = "eeGeoWebGL.jgz";

var _mapObjects = [];
var _emscriptenInitialized = false;

var createEmscriptenModule = function() {
	var Module = window.Module || {};
	window.Module = Module;
	Module["locateFile"] = function(url) {
		var absUrl = _baseUrl + url;
		return absUrl;
	};
	Module["onExit"] = function(exitCode) {
		if (exitCode === 1) {
			var message = "Error: wrld.js failed to initialize";
			if (!Module.ctx) {
				message = "Error: WebGL unavailable in this browser";
			}
			_mapObjects.forEach(function(map) {
				map.onError(message);
			});
		}
	};
};

var initializeEmscripten = function() {
	if (!_emscriptenInitialized) {
		var script = document.createElement("script");
		script.src = _baseUrl + _appName;
		document.body.appendChild(script);
		_emscriptenInitialized = true;
	}
};

var findMapContainerElement = function(elementOrId) {
	var element = elementOrId;
	var id = null;
	if (typeof elementOrId === "string") {
		id = elementOrId;
		element = document.getElementById(id);
	}
	if (!element) {
		var idError = (id === null) ? "" : (" with id '" + id + "'");
		throw "No map container found" + idError;
	}
	return element;
};

var eeGeo = {
	map: function(domElement, apiKey, options) {

		createEmscriptenModule();

		domElement = findMapContainerElement(domElement);

		var browserDocument = document;
		var browserWindow = window;
		var module = browserWindow.Module;
		var mapId = _mapObjects.length;
		var mapApiObject = new EmscriptenApi(module);
		var mapOptions = options || {};
		var map = new EegeoMapController(mapId, mapApiObject, domElement, apiKey, browserWindow, browserDocument, module, mapOptions);
		_mapObjects.push(map);

		initializeEmscripten();

		return map.leafletMap;
	},

	Marker: marker.Marker,
	marker: marker.marker,
	Popup: popup.Popup,
	popup: popup.popup,
	Polygon: polygon.Polygon,
	polygon: polygon.polygon,

	indoors: require("./public/indoors/indoors"),
	space: require("./public/space"),
	themes: require("./public/themes"),

	getMapById: function(mapId) {
		return _mapObjects[mapId];
	}
};

window.L = L;
L.eeGeo = eeGeo;

// The default image path is broken when using Browserify - it searches the script tags on the page
L.Icon.Default.imagePath = "http://cdn.leafletjs.com/leaflet/v1.0.1/images/";

module.exports = L.eeGeo;
<|endoftext|>


<commit_before>/* global Buffer */
'use strict';

const libUrl = require('url');
const Wrapper = require('./wrapper');
const Request = require('./request');
const Response = require('./response');
const utils = require('./utils');

class Nodgine {
    constructor() {
        this._middleware = [];
        this._controller = [];
        this._missingRouteController = (aRequest, aResponse) => {
            aResponse
                .setStatusCode(404)
                .write('Not Found');
        };
    }

    setMissingRouteController(aController) {
        if (typeof aController !== 'function') {
            throw new TypeError('Unmatched signature. Prease use(controller<function>)');
        }

        this._missingRouteController = aController;

        return this;
    }

    addMiddleware(aRoute, aMiddleware) {
        let route = aRoute;
        let middleware = aMiddleware;

        if (typeof aRoute === 'function') {
            middleware = aRoute;
            route = '/*';
        }

        if (typeof route !== 'string' || typeof middleware !== 'function') {
            throw new TypeError('Unmatched signature. Please use (middleware<function>) or (route<string>, middleware<function>)');
        }

        this._middleware.push(new Wrapper(route, middleware));

        return this;
    }

    addController(aRoute, aController) {
        let controller = utils.isObject(aController) ? utils.wrapServeletToFunction(aController) : aController;

        if (typeof aRoute !== 'string' || typeof controller !== 'function') {
            throw new TypeError('Unmatched signature. Please use (route<string>, controller<function>) or (route<string>, controller<object>)');
        }

        this._controller.push(new Wrapper(aRoute, controller));

        return this;
    }

    _runMiddleware(aParsedUrlPath, aRequest, aResponse) {
        let promisePointer = Promise.resolve();
        let middleWareList = this._middleware;

        for (let i = 0, len = middleWareList.length; i < len; i++) {
            promisePointer = promisePointer.then(
                middleWareList[i].runWhenRouteMatches
                    .bind(middleWareList[i], aParsedUrlPath, aRequest, aResponse)
                );
        }

        return promisePointer;
    }

    _runController(aParsedUrlPath, aRequest, aResponse) {
        let controllerList = this._controller;

        for (let i = 0, len = controllerList.length; i < len; i++) {
            let matchResult = aParsedUrlPath.match(controllerList[i].getPattern());

            if (matchResult !== null) {
                return controllerList[i].run(matchResult, aRequest, aResponse);
            }
        }
    }

    getRouter() {
        return (aRequest, aResponse) => {
            let requestBody = [];

            aRequest.on('data', (aChunk) => {
                requestBody.push(aChunk);
            });

            aRequest.on('end', () => {
                let parsedUrl = libUrl.parse(aRequest.url, true);
                let requestObject = new Request(aRequest, Buffer.concat(requestBody), parsedUrl);
                let responseObject = new Response(aResponse);

                Promise.resolve()
                    .then(() => {
                        this._runMiddleware(parsedUrl.pathname, requestObject, responseObject);
                    })
                    .then(() => {
                        this._runController(parsedUrl.pathname, requestObject, responseObject);
                    })
                    .then(() => {
                        responseObject.flush();
                    })
                    .catch((aError) => {
                        console.error(aError.message, aError.stack);

                        if (!aResponse.finished) {
                            aResponse.writeHead(500);
                            aResponse.write('Internal Server Error');
                            aResponse.end();
                        }
                    });
            });
        };
    }
}

module.exports = Nodgine;
<commit_msg>Made code better testable<commit_after>/* global Buffer */
'use strict';

const libUrl = require('url');
const Wrapper = require('./wrapper');
const Request = require('./request');
const Response = require('./response');
const utils = require('./utils');

class Nodgine {
    constructor() {
        this._middleware = [];
        this._controller = [];
        this._missingRouteController = (aRequest, aResponse) => {
            aResponse
                .setStatusCode(404)
                .write('Not Found');
        };
    }

    setMissingRouteController(aController) {
        if (typeof aController !== 'function') {
            throw new TypeError('Unmatched signature. Prease use(controller<function>)');
        }

        this._missingRouteController = aController;

        return this;
    }

    addMiddleware(aRoute, aMiddleware) {
        let route = aRoute;
        let middleware = aMiddleware;

        if (typeof aRoute === 'function') {
            middleware = aRoute;
            route = '/*';
        }

        if (typeof route !== 'string' || typeof middleware !== 'function') {
            throw new TypeError('Unmatched signature. Please use (middleware<function>) or (route<string>, middleware<function>)');
        }

        this._middleware.push(new Wrapper(route, middleware));

        return this;
    }

    addController(aRoute, aController) {
        let controller = utils.isObject(aController) ? utils.wrapServeletToFunction(aController) : aController;

        if (typeof aRoute !== 'string' || typeof controller !== 'function') {
            throw new TypeError('Unmatched signature. Please use (route<string>, controller<function>) or (route<string>, controller<object>)');
        }

        this._controller.push(new Wrapper(aRoute, controller));

        return this;
    }

    _runMiddleware(aParsedUrlPath, aRequest, aResponse) {
        let promisePointer = Promise.resolve();
        let middleWareList = this._middleware;

        for (let i = 0, len = middleWareList.length; i < len; i++) {
            promisePointer = promisePointer.then(
                middleWareList[i].runWhenRouteMatches
                    .bind(middleWareList[i], aParsedUrlPath, aRequest, aResponse)
                );
        }

        return promisePointer;
    }

    _runController(aParsedUrlPath, aRequest, aResponse) {
        let controllerList = this._controller;

        for (let i = 0, len = controllerList.length; i < len; i++) {
            let matchResult = aParsedUrlPath.match(controllerList[i].getPattern());

            if (matchResult !== null) {
                return controllerList[i].run(matchResult, aRequest, aResponse);
            }
        }
    }

    getRouter() {
        return (aRequest, aResponse) => {
            let requestBody = [];

            aRequest.on('data', (aChunk) => {
                requestBody.push(aChunk);
            });

            aRequest.on('end', () => {
                let parsedUrl = libUrl.parse(aRequest.url, true);
                let requestObject = new Request(aRequest, Buffer.concat(requestBody), parsedUrl);
                let responseObject = new Response(aResponse);

                return Promise.resolve()
                    .then(() => {
                        this._runMiddleware(parsedUrl.pathname, requestObject, responseObject);
                    })
                    .then(() => {
                        this._runController(parsedUrl.pathname, requestObject, responseObject);
                    })
                    .then(() => {
                        responseObject.flush();
                    })
                    .catch((aError) => {
                        if (!aResponse.finished) {
                            aResponse.writeHead(500);
                            aResponse.write('Internal Server Error');
                            aResponse.end();
                        }

                        throw aError;
                    });
            });
        };
    }
}

module.exports = Nodgine;
<|endoftext|>


<commit_before>var epsilon2 = 1e-12;

function cosh(x) {
  return ((x = Math.exp(x)) + 1 / x) / 2;
}

function sinh(x) {
  return ((x = Math.exp(x)) - 1 / x) / 2;
}

function tanh(x) {
  return ((x = Math.exp(2 * x)) - 1) / (x + 1);
}

export default (function zoomRho(rho, rho2, rho4) {

  // p0 = [ux0, uy0, w0]
  // p1 = [ux1, uy1, w1]
  function zoom(p0, p1) {
    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2],
        ux1 = p1[0], uy1 = p1[1], w1 = p1[2],
        dx = ux1 - ux0,
        dy = uy1 - uy0,
        d2 = dx * dx + dy * dy,
        i,
        S;

    // Special case for u0 ≅ u1.
    if (d2 < epsilon2) {
      S = Math.log(w1 / w0) / rho;
      i = function(t) {
        return [
          ux0 + t * dx,
          uy0 + t * dy,
          w0 * Math.exp(rho * t * S)
        ];
      }
    }

    // General case.
    else {
      var d1 = Math.sqrt(d2),
          b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
          b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
          r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
          r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
      S = (r1 - r0) / rho;
      i = function(t) {
        var s = t * S,
            coshr0 = cosh(r0),
            u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
        return [
          ux0 + u * dx,
          uy0 + u * dy,
          w0 * coshr0 / cosh(rho * s + r0)
        ];
      }
    }

    i.duration = S * 1000;

    return i;
  }

  zoom.rho = function(_) {
    var _1 = Math.max(1e-3, +_), _2 = _1 * _1, _4 = _2 * _2;
    return zoomRho(_1, _2, _4);
  };

  return zoom;
})(Math.SQRT2, 2, 4);
<commit_msg>the movement’s duration should not explode for small curvatures, and should be longer when the curvature is important<commit_after>var epsilon2 = 1e-12;

function cosh(x) {
  return ((x = Math.exp(x)) + 1 / x) / 2;
}

function sinh(x) {
  return ((x = Math.exp(x)) - 1 / x) / 2;
}

function tanh(x) {
  return ((x = Math.exp(2 * x)) - 1) / (x + 1);
}

export default (function zoomRho(rho, rho2, rho4) {

  // p0 = [ux0, uy0, w0]
  // p1 = [ux1, uy1, w1]
  function zoom(p0, p1) {
    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2],
        ux1 = p1[0], uy1 = p1[1], w1 = p1[2],
        dx = ux1 - ux0,
        dy = uy1 - uy0,
        d2 = dx * dx + dy * dy,
        i,
        S;

    // Special case for u0 ≅ u1.
    if (d2 < epsilon2) {
      S = Math.log(w1 / w0) / rho;
      i = function(t) {
        return [
          ux0 + t * dx,
          uy0 + t * dy,
          w0 * Math.exp(rho * t * S)
        ];
      }
    }

    // General case.
    else {
      var d1 = Math.sqrt(d2),
          b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
          b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
          r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
          r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
      S = (r1 - r0) / rho;
      i = function(t) {
        var s = t * S,
            coshr0 = cosh(r0),
            u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
        return [
          ux0 + u * dx,
          uy0 + u * dy,
          w0 * coshr0 / cosh(rho * s + r0)
        ];
      }
    }

    i.duration = S * 1000 * rho / Math.SQRT2;

    return i;
  }

  zoom.rho = function(_) {
    var _1 = Math.max(1e-3, +_), _2 = _1 * _1, _4 = _2 * _2;
    return zoomRho(_1, _2, _4);
  };

  return zoom;
})(Math.SQRT2, 2, 4);
<|endoftext|>


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

(function($) {

    /**
     * An enumeration of positions that an overlay may be assigned relative to
     * the viewport.
     * It is identical to OpenSeadragon.Placement but is kept for backward
     * compatibility.
     * @member OverlayPlacement
     * @memberof OpenSeadragon
     * @static
     * @readonly
     * @type {Object}
     * @property {Number} CENTER
     * @property {Number} TOP_LEFT
     * @property {Number} TOP
     * @property {Number} TOP_RIGHT
     * @property {Number} RIGHT
     * @property {Number} BOTTOM_RIGHT
     * @property {Number} BOTTOM
     * @property {Number} BOTTOM_LEFT
     * @property {Number} LEFT
     */
    $.OverlayPlacement = $.Placement;

    /**
     * An enumeration of possible ways to handle overlays rotation
     * @member OverlayRotationMode
     * @memberOf OpenSeadragon
     * @static
     * @readonly
     * @property {Number} NO_ROTATION The overlay ignore the viewport rotation.
     * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with
     * the viewport. If the overlay contains text, it will get rotated as well.
     * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by
     * taking the size of the bounding box of the rotated bounds.
     * Only valid for overlays with Rect location and scalable in both directions.
     */
    $.OverlayRotationMode = $.freezeObject({
        NO_ROTATION: 1,
        EXACT: 2,
        BOUNDING_BOX: 3
    });

    /**
     * @class Overlay
     * @classdesc Provides a way to float an HTML element on top of the viewer element.
     *
     * @memberof OpenSeadragon
     * @param {Object} options
     * @param {Element} options.element
     * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
     * location of the overlay on the image. If a {@link OpenSeadragon.Point}
     * is specified, the overlay will be located at this location with respect
     * to the placement option. If a {@link OpenSeadragon.Rect} is specified,
     * the overlay will be placed at this location with the corresponding width
     * and height and placement TOP_LEFT.
     * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]
     * Defines what part of the overlay should be at the specified options.location
     * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
     * @param {Boolean} [options.checkResize=true] Set to false to avoid to
     * check the size of the overlay everytime it is drawn in the directions
     * which are not scaled. It will improve performances but will cause a
     * misalignment if the overlay size changes.
     * @param {Number} [options.width] The width of the overlay in viewport
     * coordinates. If specified, the width of the overlay will be adjusted when
     * the zoom changes.
     * @param {Number} [options.height] The height of the overlay in viewport
     * coordinates. If specified, the height of the overlay will be adjusted when
     * the zoom changes.
     * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]
     * How to handle the rotation of the viewport.
     */
    $.Overlay = function(element, location, placement) {

        /**
         * onDraw callback signature used by {@link OpenSeadragon.Overlay}.
         *
         * @callback OnDrawCallback
         * @memberof OpenSeadragon.Overlay
         * @param {OpenSeadragon.Point} position
         * @param {OpenSeadragon.Point} size
         * @param {Element} element
         */

        var options;
        if ($.isPlainObject(element)) {
            options = element;
        } else {
            options = {
                element: element,
                location: location,
                placement: placement
            };
        }

        this.element = options.element;
        this.style = options.element.style;
        this._init(options);
    };

    /** @lends OpenSeadragon.Overlay.prototype */
    $.Overlay.prototype = {

        // private
        _init: function(options) {
            this.location = options.location;
            this.placement = options.placement === undefined ?
                $.Placement.TOP_LEFT : options.placement;
            this.onDraw = options.onDraw;
            this.checkResize = options.checkResize === undefined ?
                true : options.checkResize;

            // When this.width is not null, the overlay get scaled horizontally
            this.width = options.width === undefined ? null : options.width;

            // When this.height is not null, the overlay get scaled vertically
            this.height = options.height === undefined ? null : options.height;

            this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;

            // Having a rect as location is a syntactic sugar
            if (this.location instanceof $.Rect) {
                this.width = this.location.width;
                this.height = this.location.height;
                this.location = this.location.getTopLeft();
                this.placement = $.Placement.TOP_LEFT;
            }

            // Deprecated properties kept for backward compatibility.
            this.scales = this.width !== null && this.height !== null;
            this.bounds = new $.Rect(
                this.location.x, this.location.y, this.width, this.height);
            this.position = this.location;
        },

        /**
         * Internal function to adjust the position of an overlay
         * depending on it size and placement.
         * @function
         * @param {OpenSeadragon.Point} position
         * @param {OpenSeadragon.Point} size
         */
        adjust: function(position, size) {
            var properties = $.Placement.properties[this.placement];
            if (!properties) {
                return;
            }
            if (properties.isHorizontallyCentered) {
                position.x -= size.x / 2;
            } else if (properties.isRight) {
                position.x -= size.x;
            }
            if (properties.isVerticallyCentered) {
                position.y -= size.y / 2;
            } else if (properties.isBottom) {
                position.y -= size.y;
            }
        },

        /**
         * @function
         */
        destroy: function() {
            var element = this.element;
            var style = this.style;

            if (element.parentNode) {
                element.parentNode.removeChild(element);
                //this should allow us to preserve overlays when required between
                //pages
                if (element.prevElementParent) {
                    style.display = 'none';
                    //element.prevElementParent.insertBefore(
                    //    element,
                    //    element.prevNextSibling
                    //);
                    document.body.appendChild(element);
                }
            }

            // clear the onDraw callback
            this.onDraw = null;

            style.top = "";
            style.left = "";
            style.position = "";

            if (this.width !== null) {
                style.width = "";
            }
            if (this.height !== null) {
                style.height = "";
            }
            var transformOriginProp = $.getCssPropertyWithVendorPrefix(
                'transformOrigin');
            var transformProp = $.getCssPropertyWithVendorPrefix(
                'transform');
            if (transformOriginProp && transformProp) {
                style[transformOriginProp] = "";
                style[transformProp] = "";
            }
        },

        /**
         * @function
         * @param {Element} container
         */
        drawHTML: function(container, viewport) {
            var element = this.element;
            if (element.parentNode !== container) {
                //save the source parent for later if we need it
                element.prevElementParent = element.parentNode;
                element.prevNextSibling = element.nextSibling;
                container.appendChild(element);

                // this.size is used by overlays which don't get scaled in at
                // least one direction when this.checkResize is set to false.
                this.size = $.getElementSize(element);
            }

            var positionAndSize = this._getOverlayPositionAndSize(viewport);

            var position = positionAndSize.position;
            var size = this.size = positionAndSize.size;
            var rotate = positionAndSize.rotate;

            // call the onDraw callback if it exists to allow one to overwrite
            // the drawing/positioning/sizing of the overlay
            if (this.onDraw) {
                this.onDraw(position, size, this.element);
            } else {
                var style = this.style;
                style.left = position.x + "px";
                style.top = position.y + "px";
                if (this.width !== null) {
                    style.width = size.x + "px";
                }
                if (this.height !== null) {
                    style.height = size.y + "px";
                }
                var transformOriginProp = $.getCssPropertyWithVendorPrefix(
                    'transformOrigin');
                var transformProp = $.getCssPropertyWithVendorPrefix(
                    'transform');
                if (transformOriginProp && transformProp) {
                    if (rotate) {
                        style[transformOriginProp] = this._getTransformOrigin();
                        style[transformProp] = "rotate(" + rotate + "deg)";
                    } else {
                        style[transformOriginProp] = "";
                        style[transformProp] = "";
                    }
                }
                style.position = "absolute";

                if (style.display !== 'none') {
                    style.display = 'block';
                }
            }
        },

        // private
        _getOverlayPositionAndSize: function(viewport) {
            var position = viewport.pixelFromPoint(this.location, true);
            var size = this._getSizeInPixels(viewport);
            this.adjust(position, size);

            var rotate = 0;
            if (viewport.degrees &&
                this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {
                // BOUNDING_BOX is only valid if both directions get scaled.
                // Get replaced by EXACT otherwise.
                if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&
                    this.width !== null && this.height !== null) {
                    var rect = new $.Rect(position.x, position.y, size.x, size.y);
                    var boundingBox = this._getBoundingBox(rect, viewport.degrees);
                    position = boundingBox.getTopLeft();
                    size = boundingBox.getSize();
                } else {
                    rotate = viewport.degrees;
                }
            }

            return {
                position: position,
                size: size,
                rotate: rotate
            };
        },

        // private
        _getSizeInPixels: function(viewport) {
            var width = this.size.x;
            var height = this.size.y;
            if (this.width !== null || this.height !== null) {
                var scaledSize = viewport.deltaPixelsFromPointsNoRotate(
                    new $.Point(this.width || 0, this.height || 0), true);
                if (this.width !== null) {
                    width = scaledSize.x;
                }
                if (this.height !== null) {
                    height = scaledSize.y;
                }
            }
            if (this.checkResize &&
                (this.width === null || this.height === null)) {
                var eltSize = this.size = $.getElementSize(this.element);
                if (this.width === null) {
                    width = eltSize.x;
                }
                if (this.height === null) {
                    height = eltSize.y;
                }
            }
            return new $.Point(width, height);
        },

        // private
        _getBoundingBox: function(rect, degrees) {
            var refPoint = this._getPlacementPoint(rect);
            return rect.rotate(degrees, refPoint).getBoundingBox();
        },

        // private
        _getPlacementPoint: function(rect) {
            var result = new $.Point(rect.x, rect.y);
            var properties = $.Placement.properties[this.placement];
            if (properties) {
                if (properties.isHorizontallyCentered) {
                    result.x += rect.width / 2;
                } else if (properties.isRight) {
                    result.x += rect.width;
                }
                if (properties.isVerticallyCentered) {
                    result.y += rect.height / 2;
                } else if (properties.isBottom) {
                    result.y += rect.height;
                }
            }
            return result;
        },

        // private
        _getTransformOrigin: function() {
            var result = "";
            var properties = $.Placement.properties[this.placement];
            if (!properties) {
                return result;
            }
            if (properties.isLeft) {
                result = "left";
            } else if (properties.isRight) {
                result = "right";
            }
            if (properties.isTop) {
                result += " top";
            } else if (properties.isBottom) {
                result += " bottom";
            }
            return result;
        },

        /**
         * Changes the overlay settings.
         * @function
         * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location
         * If an object is specified, the options are the same than the constructor
         * except for the element which can not be changed.
         * @param {OpenSeadragon.Placement} position
         */
        update: function(location, placement) {
            var options = $.isPlainObject(location) ? location : {
                location: location,
                placement: placement
            };
            this._init({
                location: options.location || this.location,
                placement: options.placement !== undefined ?
                    options.placement : this.placement,
                onDraw: options.onDraw || this.onDraw,
                checkResize: options.checkResize || this.checkResize,
                width: options.width !== undefined ? options.width : this.width,
                height: options.height !== undefined ? options.height : this.height,
                rotationMode: options.rotationMode || this.rotationMode
            });
        },

        /**
         * Returns the current bounds of the overlay in viewport coordinates
         * @function
         * @param {OpenSeadragon.Viewport} viewport the viewport
         * @returns {OpenSeadragon.Rect} overlay bounds
         */
        getBounds: function(viewport) {
            $.console.assert(viewport,
                'A viewport must now be passed to Overlay.getBounds.');
            var width = this.width;
            var height = this.height;
            if (width === null || height === null) {
                var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);
                if (width === null) {
                    width = size.x;
                }
                if (height === null) {
                    height = size.y;
                }
            }
            var location = this.location.clone();
            this.adjust(location, new $.Point(width, height));
            return this._adjustBoundsForRotation(
                viewport, new $.Rect(location.x, location.y, width, height));
        },

        // private
        _adjustBoundsForRotation: function(viewport, bounds) {
            if (!viewport ||
                viewport.degrees === 0 ||
                this.rotationMode === $.OverlayRotationMode.EXACT) {
                return bounds;
            }
            if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {
                // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT
                if (this.width === null || this.height === null) {
                    return bounds;
                }
                // It is easier to just compute the position and size and
                // convert to viewport coordinates.
                var positionAndSize = this._getOverlayPositionAndSize(viewport);
                return viewport.viewerElementToViewportRectangle(new $.Rect(
                    positionAndSize.position.x,
                    positionAndSize.position.y,
                    positionAndSize.size.x,
                    positionAndSize.size.y));
            }

            // NO_ROTATION case
            return bounds.rotate(-viewport.degrees,
                this._getPlacementPoint(bounds));
        }
    };

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

(function($) {

    /**
     * An enumeration of positions that an overlay may be assigned relative to
     * the viewport.
     * It is identical to OpenSeadragon.Placement but is kept for backward
     * compatibility.
     * @member OverlayPlacement
     * @memberof OpenSeadragon
     * @see OpenSeadragon.Placement
     * @static
     * @readonly
     * @type {Object}
     * @property {Number} CENTER
     * @property {Number} TOP_LEFT
     * @property {Number} TOP
     * @property {Number} TOP_RIGHT
     * @property {Number} RIGHT
     * @property {Number} BOTTOM_RIGHT
     * @property {Number} BOTTOM
     * @property {Number} BOTTOM_LEFT
     * @property {Number} LEFT
     */
    $.OverlayPlacement = $.Placement;

    /**
     * An enumeration of possible ways to handle overlays rotation
     * @member OverlayRotationMode
     * @memberOf OpenSeadragon
     * @static
     * @readonly
     * @property {Number} NO_ROTATION The overlay ignore the viewport rotation.
     * @property {Number} EXACT The overlay use CSS 3 transforms to rotate with
     * the viewport. If the overlay contains text, it will get rotated as well.
     * @property {Number} BOUNDING_BOX The overlay adjusts for rotation by
     * taking the size of the bounding box of the rotated bounds.
     * Only valid for overlays with Rect location and scalable in both directions.
     */
    $.OverlayRotationMode = $.freezeObject({
        NO_ROTATION: 1,
        EXACT: 2,
        BOUNDING_BOX: 3
    });

    /**
     * @class Overlay
     * @classdesc Provides a way to float an HTML element on top of the viewer element.
     *
     * @memberof OpenSeadragon
     * @param {Object} options
     * @param {Element} options.element
     * @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
     * location of the overlay on the image. If a {@link OpenSeadragon.Point}
     * is specified, the overlay will be located at this location with respect
     * to the placement option. If a {@link OpenSeadragon.Rect} is specified,
     * the overlay will be placed at this location with the corresponding width
     * and height and placement TOP_LEFT.
     * @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]
     * Defines what part of the overlay should be at the specified options.location
     * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
     * @param {Boolean} [options.checkResize=true] Set to false to avoid to
     * check the size of the overlay everytime it is drawn in the directions
     * which are not scaled. It will improve performances but will cause a
     * misalignment if the overlay size changes.
     * @param {Number} [options.width] The width of the overlay in viewport
     * coordinates. If specified, the width of the overlay will be adjusted when
     * the zoom changes.
     * @param {Number} [options.height] The height of the overlay in viewport
     * coordinates. If specified, the height of the overlay will be adjusted when
     * the zoom changes.
     * @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]
     * How to handle the rotation of the viewport.
     */
    $.Overlay = function(element, location, placement) {

        /**
         * onDraw callback signature used by {@link OpenSeadragon.Overlay}.
         *
         * @callback OnDrawCallback
         * @memberof OpenSeadragon.Overlay
         * @param {OpenSeadragon.Point} position
         * @param {OpenSeadragon.Point} size
         * @param {Element} element
         */

        var options;
        if ($.isPlainObject(element)) {
            options = element;
        } else {
            options = {
                element: element,
                location: location,
                placement: placement
            };
        }

        this.element = options.element;
        this.style = options.element.style;
        this._init(options);
    };

    /** @lends OpenSeadragon.Overlay.prototype */
    $.Overlay.prototype = {

        // private
        _init: function(options) {
            this.location = options.location;
            this.placement = options.placement === undefined ?
                $.Placement.TOP_LEFT : options.placement;
            this.onDraw = options.onDraw;
            this.checkResize = options.checkResize === undefined ?
                true : options.checkResize;

            // When this.width is not null, the overlay get scaled horizontally
            this.width = options.width === undefined ? null : options.width;

            // When this.height is not null, the overlay get scaled vertically
            this.height = options.height === undefined ? null : options.height;

            this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;

            // Having a rect as location is a syntactic sugar
            if (this.location instanceof $.Rect) {
                this.width = this.location.width;
                this.height = this.location.height;
                this.location = this.location.getTopLeft();
                this.placement = $.Placement.TOP_LEFT;
            }

            // Deprecated properties kept for backward compatibility.
            this.scales = this.width !== null && this.height !== null;
            this.bounds = new $.Rect(
                this.location.x, this.location.y, this.width, this.height);
            this.position = this.location;
        },

        /**
         * Internal function to adjust the position of an overlay
         * depending on it size and placement.
         * @function
         * @param {OpenSeadragon.Point} position
         * @param {OpenSeadragon.Point} size
         */
        adjust: function(position, size) {
            var properties = $.Placement.properties[this.placement];
            if (!properties) {
                return;
            }
            if (properties.isHorizontallyCentered) {
                position.x -= size.x / 2;
            } else if (properties.isRight) {
                position.x -= size.x;
            }
            if (properties.isVerticallyCentered) {
                position.y -= size.y / 2;
            } else if (properties.isBottom) {
                position.y -= size.y;
            }
        },

        /**
         * @function
         */
        destroy: function() {
            var element = this.element;
            var style = this.style;

            if (element.parentNode) {
                element.parentNode.removeChild(element);
                //this should allow us to preserve overlays when required between
                //pages
                if (element.prevElementParent) {
                    style.display = 'none';
                    //element.prevElementParent.insertBefore(
                    //    element,
                    //    element.prevNextSibling
                    //);
                    document.body.appendChild(element);
                }
            }

            // clear the onDraw callback
            this.onDraw = null;

            style.top = "";
            style.left = "";
            style.position = "";

            if (this.width !== null) {
                style.width = "";
            }
            if (this.height !== null) {
                style.height = "";
            }
            var transformOriginProp = $.getCssPropertyWithVendorPrefix(
                'transformOrigin');
            var transformProp = $.getCssPropertyWithVendorPrefix(
                'transform');
            if (transformOriginProp && transformProp) {
                style[transformOriginProp] = "";
                style[transformProp] = "";
            }
        },

        /**
         * @function
         * @param {Element} container
         */
        drawHTML: function(container, viewport) {
            var element = this.element;
            if (element.parentNode !== container) {
                //save the source parent for later if we need it
                element.prevElementParent = element.parentNode;
                element.prevNextSibling = element.nextSibling;
                container.appendChild(element);

                // this.size is used by overlays which don't get scaled in at
                // least one direction when this.checkResize is set to false.
                this.size = $.getElementSize(element);
            }

            var positionAndSize = this._getOverlayPositionAndSize(viewport);

            var position = positionAndSize.position;
            var size = this.size = positionAndSize.size;
            var rotate = positionAndSize.rotate;

            // call the onDraw callback if it exists to allow one to overwrite
            // the drawing/positioning/sizing of the overlay
            if (this.onDraw) {
                this.onDraw(position, size, this.element);
            } else {
                var style = this.style;
                style.left = position.x + "px";
                style.top = position.y + "px";
                if (this.width !== null) {
                    style.width = size.x + "px";
                }
                if (this.height !== null) {
                    style.height = size.y + "px";
                }
                var transformOriginProp = $.getCssPropertyWithVendorPrefix(
                    'transformOrigin');
                var transformProp = $.getCssPropertyWithVendorPrefix(
                    'transform');
                if (transformOriginProp && transformProp) {
                    if (rotate) {
                        style[transformOriginProp] = this._getTransformOrigin();
                        style[transformProp] = "rotate(" + rotate + "deg)";
                    } else {
                        style[transformOriginProp] = "";
                        style[transformProp] = "";
                    }
                }
                style.position = "absolute";

                if (style.display !== 'none') {
                    style.display = 'block';
                }
            }
        },

        // private
        _getOverlayPositionAndSize: function(viewport) {
            var position = viewport.pixelFromPoint(this.location, true);
            var size = this._getSizeInPixels(viewport);
            this.adjust(position, size);

            var rotate = 0;
            if (viewport.degrees &&
                this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {
                // BOUNDING_BOX is only valid if both directions get scaled.
                // Get replaced by EXACT otherwise.
                if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&
                    this.width !== null && this.height !== null) {
                    var rect = new $.Rect(position.x, position.y, size.x, size.y);
                    var boundingBox = this._getBoundingBox(rect, viewport.degrees);
                    position = boundingBox.getTopLeft();
                    size = boundingBox.getSize();
                } else {
                    rotate = viewport.degrees;
                }
            }

            return {
                position: position,
                size: size,
                rotate: rotate
            };
        },

        // private
        _getSizeInPixels: function(viewport) {
            var width = this.size.x;
            var height = this.size.y;
            if (this.width !== null || this.height !== null) {
                var scaledSize = viewport.deltaPixelsFromPointsNoRotate(
                    new $.Point(this.width || 0, this.height || 0), true);
                if (this.width !== null) {
                    width = scaledSize.x;
                }
                if (this.height !== null) {
                    height = scaledSize.y;
                }
            }
            if (this.checkResize &&
                (this.width === null || this.height === null)) {
                var eltSize = this.size = $.getElementSize(this.element);
                if (this.width === null) {
                    width = eltSize.x;
                }
                if (this.height === null) {
                    height = eltSize.y;
                }
            }
            return new $.Point(width, height);
        },

        // private
        _getBoundingBox: function(rect, degrees) {
            var refPoint = this._getPlacementPoint(rect);
            return rect.rotate(degrees, refPoint).getBoundingBox();
        },

        // private
        _getPlacementPoint: function(rect) {
            var result = new $.Point(rect.x, rect.y);
            var properties = $.Placement.properties[this.placement];
            if (properties) {
                if (properties.isHorizontallyCentered) {
                    result.x += rect.width / 2;
                } else if (properties.isRight) {
                    result.x += rect.width;
                }
                if (properties.isVerticallyCentered) {
                    result.y += rect.height / 2;
                } else if (properties.isBottom) {
                    result.y += rect.height;
                }
            }
            return result;
        },

        // private
        _getTransformOrigin: function() {
            var result = "";
            var properties = $.Placement.properties[this.placement];
            if (!properties) {
                return result;
            }
            if (properties.isLeft) {
                result = "left";
            } else if (properties.isRight) {
                result = "right";
            }
            if (properties.isTop) {
                result += " top";
            } else if (properties.isBottom) {
                result += " bottom";
            }
            return result;
        },

        /**
         * Changes the overlay settings.
         * @function
         * @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location
         * If an object is specified, the options are the same than the constructor
         * except for the element which can not be changed.
         * @param {OpenSeadragon.Placement} placement
         */
        update: function(location, placement) {
            var options = $.isPlainObject(location) ? location : {
                location: location,
                placement: placement
            };
            this._init({
                location: options.location || this.location,
                placement: options.placement !== undefined ?
                    options.placement : this.placement,
                onDraw: options.onDraw || this.onDraw,
                checkResize: options.checkResize || this.checkResize,
                width: options.width !== undefined ? options.width : this.width,
                height: options.height !== undefined ? options.height : this.height,
                rotationMode: options.rotationMode || this.rotationMode
            });
        },

        /**
         * Returns the current bounds of the overlay in viewport coordinates
         * @function
         * @param {OpenSeadragon.Viewport} viewport the viewport
         * @returns {OpenSeadragon.Rect} overlay bounds
         */
        getBounds: function(viewport) {
            $.console.assert(viewport,
                'A viewport must now be passed to Overlay.getBounds.');
            var width = this.width;
            var height = this.height;
            if (width === null || height === null) {
                var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);
                if (width === null) {
                    width = size.x;
                }
                if (height === null) {
                    height = size.y;
                }
            }
            var location = this.location.clone();
            this.adjust(location, new $.Point(width, height));
            return this._adjustBoundsForRotation(
                viewport, new $.Rect(location.x, location.y, width, height));
        },

        // private
        _adjustBoundsForRotation: function(viewport, bounds) {
            if (!viewport ||
                viewport.degrees === 0 ||
                this.rotationMode === $.OverlayRotationMode.EXACT) {
                return bounds;
            }
            if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {
                // If overlay not fully scalable, BOUNDING_BOX falls back to EXACT
                if (this.width === null || this.height === null) {
                    return bounds;
                }
                // It is easier to just compute the position and size and
                // convert to viewport coordinates.
                var positionAndSize = this._getOverlayPositionAndSize(viewport);
                return viewport.viewerElementToViewportRectangle(new $.Rect(
                    positionAndSize.position.x,
                    positionAndSize.position.y,
                    positionAndSize.size.x,
                    positionAndSize.size.y));
            }

            // NO_ROTATION case
            return bounds.rotate(-viewport.degrees,
                this._getPlacementPoint(bounds));
        }
    };

}(OpenSeadragon));
<|endoftext|>


<commit_before>import File from './file';
const diff = require('simple-diff');
const jp = require('jsonpath');

function moduleNames(object) {
  if (object === undefined) return new Set();

  const modules = new Set();

  Object.keys(object).forEach(k => {
    const v = object[k];
    if (typeof v === 'string') {
      modules.add(v);
    } else if (Array.isArray(v)) {
      v.forEach(e => {
        if (typeof e === 'string') {
          modules.add(e);
        }
      });
    }
  });

  return modules;
}

/**
 * order in which json keys are written
 */
const sortedKeys = [
  'name',
  'version',
  'publishConfig',
  'main',
  'module',
  'description',
  'keywords',
  'author',
  'contributors',
  'license',
  'bin',
  'scripts',
  'dependencies',
  'devDependencies',
  'peerDependencies',
  'optionalDependencies',
  'engines',
  'repository',
  'bugs',
  'homepage',
  'release',
  'ava',
  'nyc',
  'xo',
  'template'
];

/**
 * Merger for package.json
 */
export default class Package extends File {
  static matchesFileName(name) {
    return name.match(/^package\.json$/);
  }

  optionalDevModules(modules = new Set()) {
    return new Set(['cracks', 'dont-crack'].filter(m => modules.has(m)));
  }

  async usedDevModules(content) {
    content = await content;

    const pkg = content.length === 0 ? {} : JSON.parse(content);

    return moduleNames(pkg.release);
  }

  /**
   * Deliver some key properties
   * @param {Context} context
   * @return {Object}
   */
  async properties(context) {
    try {
      const content = await this.originalContent(context);
      const pkg = JSON.parse(content);

      const properties = {
        npm: { name: pkg.name, fullName: pkg.name }
      };

      const m = pkg.name.match(/^(\@[^\/]+)\/(.*)/);
      if (m) {
        properties.npm.organization = m[1];
        properties.npm.name = m[2];
      }

      if (pkg.template !== undefined && pkg.template.repository !== undefined) {
        properties.templateRepo = pkg.template.repository.url;
      }

      return properties;
    } catch (e) {}

    return {};
  }

  async mergeContent(context, original, templateContent) {
    const originalLastChar = original[original.length - 1];
    const originalTemplate = JSON.parse(templateContent);

    const targetRepository = context.targetBranch.repository;

    let target =
      original === undefined || original === '' ? {} : JSON.parse(original);

    const template = Object.assign({}, originalTemplate, {
      repository: {
        type: targetRepository.type,
        url: targetRepository.url
      },
      bugs: {
        url: context.targetBranch.issuesURL
      },
      homepage: context.targetBranch.homePageURL,
      template: {
        repository: {
          url: context.templateBranch.url
        }
      }
    });
    template.template = Object.assign({}, target.template, template.template);

    let messages = [];
    const properties = context.properties;

    if (target.name === undefined || target.name === '') {
      const m = targetRepository.name.match(/^([^\/]+)\/(.*)/);
      target.name = m ? m[2] : context.targetBranch.name;
    }

    if (target.module !== undefined && !target.module.match(/\{\{module\}\}/)) {
      properties.module = target.module;
    }

    properties.main =
      target.main && !target.main.match(/\{\{main\}\}/)
        ? target.main
        : 'dist/index.js';

    const slots = {
      repository: 'chore(package): correct repository url',
      bugs: 'chore(package): correct bugs url',
      homepage: 'chore(package): homepage',
      template: 'chore(package): set template repo'
    };
    Object.keys(slots).forEach(key => {
      if (diff(target[key], template[key]).length > 0) {
        messages.push(slots[key]);
        target[key] = template[key];
      }
    });

    let buildOutput;
    const extraBuilds = {};

    if (target.scripts !== undefined) {
      Object.keys(target.scripts).forEach(key => {
        const script = target.scripts[key];
        const ma = script.match(/--output=([^\s]+)/);
        if (ma) {
          buildOutput = ma[1];
        }

        if (template.scripts !== undefined && key !== 'semantic-release') {
          const templateScript = template.scripts[key];
          if (templateScript !== undefined) {
            const mb = script.match(/&&\s*(.+)$/);
            if (mb && !templateScript.includes(mb[1])) {
              extraBuilds[key] = mb[1];
            }
          }
        }
      });
    }

    const usedDevModules = await context.usedDevModules();

    context.logger.debug(
      `usedDevModules: ${Array.from(usedDevModules).join(',')}`
    );

    const optionalDevModules = context.optionalDevModules(usedDevModules);

    context.logger.debug(
      `optionalDevModules: ${Array.from(optionalDevModules).join(',')}`
    );

    const deepProperties = {
      devDependencies: {},
      dependencies: {},
      peerDependencies: {},
      optionalDependencies: {},
      scripts: {},
      engines: {}
    };

    Object.keys(deepProperties).forEach(category => {
      if (template[category] !== undefined) {
        Object.keys(template[category]).forEach(d => {
          const tp = context.expand(template[category][d]);
          if (tp === '-') {
            if (
              target[category] !== undefined &&
              target[category][d] !== undefined
            ) {
              messages.push(
                `chore(${category}): remove ${d}@${target[category][d]}`
              );
              delete target[category][d];
            }
          } else {
            if (
              category === 'devDependencies' &&
              target.dependencies !== undefined &&
              target.dependencies[d] === tp
            ) {
              // do not include dev dependency if regular dependency is already present
            } else {
              if (
                target[category] !== undefined &&
                tp !== target[category][d]
              ) {
                messages.push(
                  target[category][d] === undefined
                    ? `chore(${category}): add ${d}@${tp} from template`
                    : `chore(${category}): update ${d}@${tp} from template`
                );
                target[category][d] = tp;
              }
            }
          }
        });
      }
    });

    Object.keys(template).forEach(p => {
      if (target[p] === undefined) {
        target[p] = template[p];
      }
    });

    if (target.scripts !== undefined && target.scripts.prepare) {
      if (buildOutput !== undefined) {
        target.scripts.prepare = target.scripts.prepare.replace(
          /--output=([^\s]+)/,
          `--output=${buildOutput}`
        );
      }
    }

    Object.keys(extraBuilds).forEach(key => {
      target.scripts[key] += ` && ${extraBuilds[key]}`;
    });

    if (target.module === '{{module}}') {
      delete target.module;
    }

    if (
      target.contributors !== undefined &&
      target.author !== undefined &&
      target.author.name !== undefined
    ) {
      const m = target.author.name.match(/(^[^<]+)<([^>]+)>/);
      if (m !== undefined) {
        const name = String(m[1]).replace(/^\s+|\s+$/g, '');
        const email = m[2];

        if (
          target.contributors.find(c => c.name === name && c.email === email)
        ) {
          delete target.author;
        }
      }
    }

    const toBeDeletedModules =
      target.devDependencies === undefined
        ? []
        : Array.from(
            context.optionalDevModules(
              new Set(Object.keys(target.devDependencies))
            )
          ).filter(m => !usedDevModules.has(m));

    toBeDeletedModules.forEach(d => {
      messages = messages.filter(
        m => !m.startsWith(`chore(devDependencies): add ${d}@`)
      );
      delete target.devDependencies[d];
    });

    target = deleter(target, template, messages, []);

    if (target.keywords !== undefined) {
      delete target.keywords['npm-package-template'];
    }

    if (
      originalTemplate.template !== undefined &&
      originalTemplate.template.keywords !== undefined
    ) {
      Object.keys(originalTemplate.template.keywords).forEach(r =>
        addKeyword(
          target,
          new RegExp(r),
          originalTemplate.template.keywords[r],
          messages
        )
      );
    }

    removeKeyword(target, ['null', null, undefined], messages);

    if (this.options !== undefined) {
      const actions = this.options.actions;
      if (actions !== undefined) {
        actions.forEach(action => {
          if (action.op === 'replace') {
            const value = jp.value(template, action.path);
            if (value !== undefined) {
              const oldValue = jp.value(target, action.path);
              if (oldValue !== value) {
                jp.value(target, action.path, value);
                messages.push(
                  `chore(package): set ${action.path}='${value}' as in template`
                );
              }
            }
          }
        });
      }
    }

    if (messages.length === 0) {
      messages.push('chore(package): update package.json from template');
    }

    target = context.expand(target);
    const sortedTarget = {};

    sortedKeys.forEach(key => {
      if (target[key] !== undefined) {
        sortedTarget[key] = target[key];
      }
    });

    Object.keys(target).forEach(key => {
      if (sortedTarget[key] === undefined) {
        sortedTarget[key] = target[key];
      }
    });

    let newContent = JSON.stringify(sortedTarget, undefined, 2);
    const lastChar = newContent[newContent.length - 1];

    // keep trailing newline
    if (originalLastChar === '\n' && lastChar === '}') {
      newContent += '\n';
    }

    return {
      content: newContent,
      messages,
      changed: original !== newContent
    };
  }
}

function deleter(object, reference, messages, path) {
  if (
    typeof object === 'string' ||
    object instanceof String ||
    object === true ||
    object === false ||
    object === undefined ||
    object === null ||
    typeof object === 'number' ||
    object instanceof Number
  ) {
    return object;
  }

  if (Array.isArray(object)) {
    return object.map((e, i) => {
      path.push(i);
      const n = deleter(
        object[i],
        Array.isArray(reference) ? reference[i] : undefined,
        messages,
        path
      );
      path.pop();
      return n;
    });
  }

  if (reference) {
    Object.keys(reference).forEach(key => {
      path.push(key);

      if (reference[key] === '--delete--' && object[key] !== undefined) {
        if (object[key] !== '--delete--') {
          messages.push(`chore(npm): delete ${path.join('.')}`);
        }
        delete object[key];
      } else {
        object[key] = deleter(object[key], reference[key], messages, path);
      }
      path.pop();
    });
  }

  return object;
}

function removeKeyword(pkg, keywords, messages) {
  if (pkg.keywords !== undefined) {
    keywords.forEach(keyword => {
      if (pkg.keywords.find(k => k === keyword)) {
        messages.push(`docs(package): remove keyword ${keyword}`);
        pkg.keywords = pkg.keywords.filter(k => k !== keyword);
      }
    });

    if (pkg.keywords[0] === null || pkg.keywords[0] === undefined) {
      messages.push(`docs(package): remove keyword null`);
      pkg.keywords = [];
    }
  }
}

function addKeyword(pkg, regex, keyword, messages) {
  if (keyword === undefined || keyword === null || keyword === 'null') {
    return;
  }

  if (pkg.name.match(regex)) {
    if (pkg.keywords === undefined) {
      pkg.keywords = [];
    }
    if (!pkg.keywords.find(k => k === keyword)) {
      messages.push(`docs(package): add keyword ${keyword}`);
      pkg.keywords.push(keyword);
    }
  }
}
<commit_msg>fix(package): remove outdated specia semantic-release script handling<commit_after>import File from './file';
const diff = require('simple-diff');
const jp = require('jsonpath');

function moduleNames(object) {
  if (object === undefined) return new Set();

  const modules = new Set();

  Object.keys(object).forEach(k => {
    const v = object[k];
    if (typeof v === 'string') {
      modules.add(v);
    } else if (Array.isArray(v)) {
      v.forEach(e => {
        if (typeof e === 'string') {
          modules.add(e);
        }
      });
    }
  });

  return modules;
}

/**
 * order in which json keys are written
 */
const sortedKeys = [
  'name',
  'version',
  'publishConfig',
  'main',
  'module',
  'description',
  'keywords',
  'author',
  'contributors',
  'license',
  'bin',
  'scripts',
  'dependencies',
  'devDependencies',
  'peerDependencies',
  'optionalDependencies',
  'engines',
  'repository',
  'bugs',
  'homepage',
  'release',
  'ava',
  'nyc',
  'xo',
  'template'
];

/**
 * Merger for package.json
 */
export default class Package extends File {
  static matchesFileName(name) {
    return name.match(/^package\.json$/);
  }

  optionalDevModules(modules = new Set()) {
    return new Set(['cracks', 'dont-crack'].filter(m => modules.has(m)));
  }

  async usedDevModules(content) {
    content = await content;

    const pkg = content.length === 0 ? {} : JSON.parse(content);

    return moduleNames(pkg.release);
  }

  /**
   * Deliver some key properties
   * @param {Context} context
   * @return {Object}
   */
  async properties(context) {
    try {
      const content = await this.originalContent(context);
      const pkg = JSON.parse(content);

      const properties = {
        npm: { name: pkg.name, fullName: pkg.name }
      };

      const m = pkg.name.match(/^(\@[^\/]+)\/(.*)/);
      if (m) {
        properties.npm.organization = m[1];
        properties.npm.name = m[2];
      }

      if (pkg.template !== undefined && pkg.template.repository !== undefined) {
        properties.templateRepo = pkg.template.repository.url;
      }

      return properties;
    } catch (e) {}

    return {};
  }

  async mergeContent(context, original, templateContent) {
    const originalLastChar = original[original.length - 1];
    const originalTemplate = JSON.parse(templateContent);

    const targetRepository = context.targetBranch.repository;

    let target =
      original === undefined || original === '' ? {} : JSON.parse(original);

    const template = Object.assign({}, originalTemplate, {
      repository: {
        type: targetRepository.type,
        url: targetRepository.url
      },
      bugs: {
        url: context.targetBranch.issuesURL
      },
      homepage: context.targetBranch.homePageURL,
      template: {
        repository: {
          url: context.templateBranch.url
        }
      }
    });
    template.template = Object.assign({}, target.template, template.template);

    let messages = [];
    const properties = context.properties;

    if (target.name === undefined || target.name === '') {
      const m = targetRepository.name.match(/^([^\/]+)\/(.*)/);
      target.name = m ? m[2] : context.targetBranch.name;
    }

    if (target.module !== undefined && !target.module.match(/\{\{module\}\}/)) {
      properties.module = target.module;
    }

    properties.main =
      target.main && !target.main.match(/\{\{main\}\}/)
        ? target.main
        : 'dist/index.js';

    const slots = {
      repository: 'chore(package): correct repository url',
      bugs: 'chore(package): correct bugs url',
      homepage: 'chore(package): homepage',
      template: 'chore(package): set template repo'
    };
    Object.keys(slots).forEach(key => {
      if (diff(target[key], template[key]).length > 0) {
        messages.push(slots[key]);
        target[key] = template[key];
      }
    });

    let buildOutput;
    const extraBuilds = {};

    if (target.scripts !== undefined) {
      Object.keys(target.scripts).forEach(key => {
        const script = target.scripts[key];
        const ma = script.match(/--output=([^\s]+)/);
        if (ma) {
          buildOutput = ma[1];
        }

        if (template.scripts !== undefined) {
          const templateScript = template.scripts[key];
          if (templateScript !== undefined) {
            //const parts = script.split(/\s*&&\s*/);
            const mb = script.match(/&&\s*(.+)$/);
            if (mb && !templateScript.includes(mb[1])) {
              extraBuilds[key] = mb[1];
            }
          }
        }
      });
    }

    const usedDevModules = await context.usedDevModules();

    context.logger.debug(
      `usedDevModules: ${Array.from(usedDevModules).join(',')}`
    );

    const optionalDevModules = context.optionalDevModules(usedDevModules);

    context.logger.debug(
      `optionalDevModules: ${Array.from(optionalDevModules).join(',')}`
    );

    const deepProperties = {
      devDependencies: {},
      dependencies: {},
      peerDependencies: {},
      optionalDependencies: {},
      scripts: {},
      engines: {}
    };

    Object.keys(deepProperties).forEach(category => {
      if (template[category] !== undefined) {
        Object.keys(template[category]).forEach(d => {
          const tp = context.expand(template[category][d]);
          if (tp === '-') {
            if (
              target[category] !== undefined &&
              target[category][d] !== undefined
            ) {
              messages.push(
                `chore(${category}): remove ${d}@${target[category][d]}`
              );
              delete target[category][d];
            }
          } else {
            if (
              category === 'devDependencies' &&
              target.dependencies !== undefined &&
              target.dependencies[d] === tp
            ) {
              // do not include dev dependency if regular dependency is already present
            } else {
              if (
                target[category] !== undefined &&
                tp !== target[category][d]
              ) {
                messages.push(
                  target[category][d] === undefined
                    ? `chore(${category}): add ${d}@${tp} from template`
                    : `chore(${category}): update ${d}@${tp} from template`
                );
                target[category][d] = tp;
              }
            }
          }
        });
      }
    });

    Object.keys(template).forEach(p => {
      if (target[p] === undefined) {
        target[p] = template[p];
      }
    });

    if (target.scripts !== undefined && target.scripts.prepare) {
      if (buildOutput !== undefined) {
        target.scripts.prepare = target.scripts.prepare.replace(
          /--output=([^\s]+)/,
          `--output=${buildOutput}`
        );
      }
    }

    Object.keys(extraBuilds).forEach(key => {
      target.scripts[key] += ` && ${extraBuilds[key]}`;
    });

    if (target.module === '{{module}}') {
      delete target.module;
    }

    if (
      target.contributors !== undefined &&
      target.author !== undefined &&
      target.author.name !== undefined
    ) {
      const m = target.author.name.match(/(^[^<]+)<([^>]+)>/);
      if (m !== undefined) {
        const name = String(m[1]).replace(/^\s+|\s+$/g, '');
        const email = m[2];

        if (
          target.contributors.find(c => c.name === name && c.email === email)
        ) {
          delete target.author;
        }
      }
    }

    const toBeDeletedModules =
      target.devDependencies === undefined
        ? []
        : Array.from(
            context.optionalDevModules(
              new Set(Object.keys(target.devDependencies))
            )
          ).filter(m => !usedDevModules.has(m));

    toBeDeletedModules.forEach(d => {
      messages = messages.filter(
        m => !m.startsWith(`chore(devDependencies): add ${d}@`)
      );
      delete target.devDependencies[d];
    });

    target = deleter(target, template, messages, []);

    if (target.keywords !== undefined) {
      delete target.keywords['npm-package-template'];
    }

    if (
      originalTemplate.template !== undefined &&
      originalTemplate.template.keywords !== undefined
    ) {
      Object.keys(originalTemplate.template.keywords).forEach(r =>
        addKeyword(
          target,
          new RegExp(r),
          originalTemplate.template.keywords[r],
          messages
        )
      );
    }

    removeKeyword(target, ['null', null, undefined], messages);

    if (this.options !== undefined) {
      const actions = this.options.actions;
      if (actions !== undefined) {
        actions.forEach(action => {
          if (action.op === 'replace') {
            const value = jp.value(template, action.path);
            if (value !== undefined) {
              const oldValue = jp.value(target, action.path);
              if (oldValue !== value) {
                jp.value(target, action.path, value);
                messages.push(
                  `chore(package): set ${action.path}='${value}' as in template`
                );
              }
            }
          }
        });
      }
    }

    if (messages.length === 0) {
      messages.push('chore(package): update package.json from template');
    }

    target = context.expand(target);
    const sortedTarget = {};

    sortedKeys.forEach(key => {
      if (target[key] !== undefined) {
        sortedTarget[key] = target[key];
      }
    });

    Object.keys(target).forEach(key => {
      if (sortedTarget[key] === undefined) {
        sortedTarget[key] = target[key];
      }
    });

    let newContent = JSON.stringify(sortedTarget, undefined, 2);
    const lastChar = newContent[newContent.length - 1];

    // keep trailing newline
    if (originalLastChar === '\n' && lastChar === '}') {
      newContent += '\n';
    }

    return {
      content: newContent,
      messages,
      changed: original !== newContent
    };
  }
}

function deleter(object, reference, messages, path) {
  if (
    typeof object === 'string' ||
    object instanceof String ||
    object === true ||
    object === false ||
    object === undefined ||
    object === null ||
    typeof object === 'number' ||
    object instanceof Number
  ) {
    return object;
  }

  if (Array.isArray(object)) {
    return object.map((e, i) => {
      path.push(i);
      const n = deleter(
        object[i],
        Array.isArray(reference) ? reference[i] : undefined,
        messages,
        path
      );
      path.pop();
      return n;
    });
  }

  if (reference) {
    Object.keys(reference).forEach(key => {
      path.push(key);

      if (reference[key] === '--delete--' && object[key] !== undefined) {
        if (object[key] !== '--delete--') {
          messages.push(`chore(npm): delete ${path.join('.')}`);
        }
        delete object[key];
      } else {
        object[key] = deleter(object[key], reference[key], messages, path);
      }
      path.pop();
    });
  }

  return object;
}

function removeKeyword(pkg, keywords, messages) {
  if (pkg.keywords !== undefined) {
    keywords.forEach(keyword => {
      if (pkg.keywords.find(k => k === keyword)) {
        messages.push(`docs(package): remove keyword ${keyword}`);
        pkg.keywords = pkg.keywords.filter(k => k !== keyword);
      }
    });

    if (pkg.keywords[0] === null || pkg.keywords[0] === undefined) {
      messages.push(`docs(package): remove keyword null`);
      pkg.keywords = [];
    }
  }
}

function addKeyword(pkg, regex, keyword, messages) {
  if (keyword === undefined || keyword === null || keyword === 'null') {
    return;
  }

  if (pkg.name.match(regex)) {
    if (pkg.keywords === undefined) {
      pkg.keywords = [];
    }
    if (!pkg.keywords.find(k => k === keyword)) {
      messages.push(`docs(package): add keyword ${keyword}`);
      pkg.keywords.push(keyword);
    }
  }
}
<|endoftext|>