<commit_before>// Copyright 2015, EMC, Inc.

'use strict';

var di = require('di');

module.exports = ipmiCatalogJobFactory;
di.annotate(ipmiCatalogJobFactory, new di.Provide('Job.LocalIpmi.Catalog'));
    di.annotate(ipmiCatalogJobFactory,
    new di.Inject(
        'Job.Base',
        'JobUtils.CommandParser',
        'Services.Waterline',
        'Logger',
        'Promise',
        'Assert',
        'Util',
        '_',
        'Services.Encryption',
        'ChildProcess',
        'Services.Lookup',
        'Constants'
    )
);
function ipmiCatalogJobFactory(
    BaseJob,
    parser,
    waterline,
    Logger,
    Promise,
    assert,
    util,
    _,
    encryption,
    ChildProcess,
    lookup,
    Constants
) {
    var logger = Logger.initialize(ipmiCatalogJobFactory);

    /**
     * @param {Object} options
     * @param {Object} context
     * @param {String} taskId
     * @constructor
     */
    function IpmiCatalogJob(options, context, taskId) {
        IpmiCatalogJob.super_.call(this, logger, options, context, taskId);

        assert.string(this.context.target);

        this.nodeId = this.context.target;
        this.commands = options.commands;
        this.commands = _.isArray(options.commands) ? options.commands : [options.commands];
    }
    util.inherits(IpmiCatalogJob, BaseJob);

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype._run = function() {
        var self = this;
        var cmdResp = [];

        return waterline.obms.findByNode(self.nodeId, 'ipmi-obm-service', true)
            .then(function(settings){
                if (settings.config.host && Constants.Regex.MacAddress.test(settings.config.host)) {
                    return lookup.macAddressToIp(settings.config.host).then(function (ipAddress) {
                        settings.config.host = ipAddress;
                        return settings;
                    });
                }

                return settings;
            })
            .then(function (obmSettings) {
                assert.object(obmSettings, 'No OBM service available.');
                assert.ok(!_.isEmpty(obmSettings.config), 'No OBM service config available.');

                return Promise.each(self.commands, function (cmd) {
                    return self.runCommand(obmSettings.config, cmd)
                        .then(function (ret) {
                            cmdResp.push(ret);
                        });
                });
            })
            .then(function () {
                return cmdResp;
            })
            .then(self.handleResponse.bind(self))
            .then(function () {
                self._done();
            })
            .catch(function (e) {
                self._done(e);
            });
    };

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype.formatCmd = function (obmSettings, cmd) {
        assert.object(obmSettings);

        var command;

        if (typeof cmd === 'string') {
            command = cmd;
        } else if (_.has(cmd, 'command') && typeof cmd.command === 'string') {
            command = cmd.command;
        } else {
            return null;
        }

        return {
            oriCmd: command,
            newCmd: [
                '-I', 'lanplus',
                '-U', obmSettings.user,
                '-P', obmSettings.password,
                '-H', obmSettings.host
            ].concat(command.split(" "))
        };
    };

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype.runCommand = function (obmSetting, cmd) {
        var self = this;

        var formatedCmd = self.formatCmd(obmSetting, cmd);

        assert.arrayOfString(formatedCmd.newCmd);

        return Promise.resolve()
        .then(function() {
            var childProcess = new ChildProcess(
                                            'ipmitool',
                                            formatedCmd.newCmd,
                                            {},
                                            cmd.acceptedResponseCodes);

            logger.debug("Sending command to node.", {
                command: formatedCmd.newCmd.join(),
                nodeId: self.nodeId
            });

            return childProcess.run({ retries: 0, delay: 0 });
        })
        .then(function(ret) {
            // Modify the command to match the format in command parser
            ret.cmd = "sudo ipmitool ".concat(formatedCmd.oriCmd);

            logger.debug("Received respond from node.", {
                nodeId: self.nodeId,
                error: ret.error
            });
            return ret;
        });
    };

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype.handleResponse = function(result) {
        var self = this;

        return parser.parseTasks(result)
        .spread(function() {
            var addCatalogPromises = [];
            var lookupPromises = [];

            _.forEach(arguments, function(result) {
                if (result.error) {
                    logger.error("Failed to parse data for " + result.source, {
                        error: result.error,
                        result: result,
                    });
                } else {
                    if (result.store) {
                        addCatalogPromises.push(
                            Promise.resolve(waterline.catalogs.create({
                                node: self.nodeId,
                                source: result.source,
                                data: result.data
                            }))
                        );
                    } else {
                        logger.debug("Catalog result for " + result.source +
                            " has not been marked as significant. Not storing.");
                    }
                }
            });

            return [addCatalogPromises, lookupPromises];
        }).catch(function(err) {
            logger.error("Job error processing catalog output.", {
                error: err,
                id: self.nodeId,
                taskContext: self.context
            });
        });
    };

    return IpmiCatalogJob;
}
<commit_msg>Move sanity checks up.<commit_after>// Copyright 2015, EMC, Inc.

'use strict';

var di = require('di');

module.exports = ipmiCatalogJobFactory;
di.annotate(ipmiCatalogJobFactory, new di.Provide('Job.LocalIpmi.Catalog'));
    di.annotate(ipmiCatalogJobFactory,
    new di.Inject(
        'Job.Base',
        'JobUtils.CommandParser',
        'Services.Waterline',
        'Logger',
        'Promise',
        'Assert',
        'Util',
        '_',
        'Services.Encryption',
        'ChildProcess',
        'Services.Lookup',
        'Constants'
    )
);
function ipmiCatalogJobFactory(
    BaseJob,
    parser,
    waterline,
    Logger,
    Promise,
    assert,
    util,
    _,
    encryption,
    ChildProcess,
    lookup,
    Constants
) {
    var logger = Logger.initialize(ipmiCatalogJobFactory);

    /**
     * @param {Object} options
     * @param {Object} context
     * @param {String} taskId
     * @constructor
     */
    function IpmiCatalogJob(options, context, taskId) {
        IpmiCatalogJob.super_.call(this, logger, options, context, taskId);

        assert.string(this.context.target);

        this.nodeId = this.context.target;
        this.commands = options.commands;
        this.commands = _.isArray(options.commands) ? options.commands : [options.commands];
    }
    util.inherits(IpmiCatalogJob, BaseJob);

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype._run = function() {
        var self = this;
        var cmdResp = [];

        return waterline.obms.findByNode(self.nodeId, 'ipmi-obm-service', true)
            .then(function(settings){
                assert.object(settings, 'No OBM service available.');
                assert.ok(!_.isEmpty(settings.config), 'No OBM service config available.');

                if (settings.config.host && Constants.Regex.MacAddress.test(settings.config.host)) {
                    return lookup.macAddressToIp(settings.config.host).then(function (ipAddress) {
                        settings.config.host = ipAddress;
                        return settings;
                    });
                }

                return settings;
            })
            .then(function (obmSettings) {
                return Promise.each(self.commands, function (cmd) {
                    return self.runCommand(obmSettings.config, cmd)
                        .then(function (ret) {
                            cmdResp.push(ret);
                        });
                });
            })
            .then(function () {
                return cmdResp;
            })
            .then(self.handleResponse.bind(self))
            .then(function () {
                self._done();
            })
            .catch(function (e) {
                self._done(e);
            });
    };

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype.formatCmd = function (obmSettings, cmd) {
        assert.object(obmSettings);

        var command;

        if (typeof cmd === 'string') {
            command = cmd;
        } else if (_.has(cmd, 'command') && typeof cmd.command === 'string') {
            command = cmd.command;
        } else {
            return null;
        }

        return {
            oriCmd: command,
            newCmd: [
                '-I', 'lanplus',
                '-U', obmSettings.user,
                '-P', obmSettings.password,
                '-H', obmSettings.host
            ].concat(command.split(" "))
        };
    };

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype.runCommand = function (obmSetting, cmd) {
        var self = this;

        var formatedCmd = self.formatCmd(obmSetting, cmd);

        assert.arrayOfString(formatedCmd.newCmd);

        return Promise.resolve()
        .then(function() {
            var childProcess = new ChildProcess(
                                            'ipmitool',
                                            formatedCmd.newCmd,
                                            {},
                                            cmd.acceptedResponseCodes);

            logger.debug("Sending command to node.", {
                command: formatedCmd.newCmd.join(),
                nodeId: self.nodeId
            });

            return childProcess.run({ retries: 0, delay: 0 });
        })
        .then(function(ret) {
            // Modify the command to match the format in command parser
            ret.cmd = "sudo ipmitool ".concat(formatedCmd.oriCmd);

            logger.debug("Received respond from node.", {
                nodeId: self.nodeId,
                error: ret.error
            });
            return ret;
        });
    };

    /**
     * @memberOf IpmiCommandJob
     */
    IpmiCatalogJob.prototype.handleResponse = function(result) {
        var self = this;

        return parser.parseTasks(result)
        .spread(function() {
            var addCatalogPromises = [];
            var lookupPromises = [];

            _.forEach(arguments, function(result) {
                if (result.error) {
                    logger.error("Failed to parse data for " + result.source, {
                        error: result.error,
                        result: result,
                    });
                } else {
                    if (result.store) {
                        addCatalogPromises.push(
                            Promise.resolve(waterline.catalogs.create({
                                node: self.nodeId,
                                source: result.source,
                                data: result.data
                            }))
                        );
                    } else {
                        logger.debug("Catalog result for " + result.source +
                            " has not been marked as significant. Not storing.");
                    }
                }
            });

            return [addCatalogPromises, lookupPromises];
        }).catch(function(err) {
            logger.error("Job error processing catalog output.", {
                error: err,
                id: self.nodeId,
                taskContext: self.context
            });
        });
    };

    return IpmiCatalogJob;
}
<|endoftext|>


<commit_before>/** SoraMame block is block-type code editor mock-up for tiny script.
	Copyright 2014-2015 Yutaka Kachi released under MIT license.
 */

	(function() {
		var soramame = {};
		var expDialog_hundle = {}; //for Express Line Editor

		/** clearTrash =============

		 */
		soramame.clearTrash = function() {
			if(window.confirm("Clear trash?")) {
				$("#trash-can").empty();
			}
		};

		/** Serialize and transfer from blocks to code.  =============
		*/
		var getCodeBlock = function() {
			var data = $('.serialize .code-body').text();
			return js_beautify(data);
		};

		soramame.setSerializeBlock  = function() {
			var codeText = getCodeBlock();
			$("pre code").text(codeText);

			$('pre code').each(function(i, block) {
				hljs.highlightBlock(block);
			});
		};

		soramame.execCodeBlock = function() {
			var codeText = getCodeBlock();
			soramame.exec = new Function(codeText);
			soramame.exec();
		};

		/** Express Line Editor for SoraMame.Block =============
			Using Modal.js of bootstrap
		 */
		var openExpDialog = function(expBody) {
			$('#expModalDialog').modal();
			var textArea = $('#expModalText');
			textArea.attr("size",(expBody.length < 10)? 10 : expBody.length * 2);
			textArea.val(expBody);
		};

		$('span.exp-body').click(function() {
			expDialog_hundle = $(this);
			openExpDialog(expDialog_hundle.text());
		})

		/** When open dialog, focus on textbox for bootstrap3 */
		$('#expModalDialog').on('shown.bs.modal', function () {
			$('#expModalText').focus();
		});

		$('#btn_ok_expModalDialog').click(function() {
			var strTextBox = $('#expModalText').val();
			expDialog_hundle.text(strTextBox);
			var itemName = expDialog_hundle.attr('class').split(" ")[1];
			expDialog_hundle.parent().next().find('span.' + itemName).text(strTextBox)
			$('#expModalDialog').modal('hide');
		})

		/** add Single Global var. */
		if (typeof window.SORAMAME_BLOCK == "undefined") {
			window.SORAMAME_BLOCK = soramame;
		}

		/** connect for SoraMame.Block and jquery-sortable =============
		 */
		$('ol.pallet-code').sortable({
			group: 'connect-area',
			drop: false,
			onDragStart: function (item, container, _super) {
				// Duplicate items of the no drop area
				if(!container.options.drop) {
					item.clone(true).insertAfter(item)
				}
				_super(item);
			},
		});

		$('ol.block').sortable({
			group: 'connect-area',
		});

		$('ol.trash-code').sortable({
			group: 'connect-area',
		});

	})()

	SORAMAME_BLOCK.app = {
		msg: "Hello SoraMame Block"

	};<commit_msg>change some mistake<commit_after>/** SoraMame block is block-type code editor mock-up for tiny script.
	Copyright 2014-2015 Yutaka Kachi released under MIT license.
 */

	(function() {
		var soramame = {};
		var expDialog_hundle = {}; //for Express Line Editor

		/** clearTrash =============

		 */
		soramame.clearTrash = function() {
			if(window.confirm("Clear trash?")) {
				$("#trash-can").empty();
			}
		};

		/** Serialize and transfer from blocks to code.  =============
		*/
		var getCodeBlock = function() {
			var data = $('.serialize .code-body').text();
			return js_beautify(data);
		};

		soramame.setSerializeBlock  = function() {
			var codeText = getCodeBlock();
			$("pre code").text(codeText);

			$('pre code').each(function(i, block) {
				hljs.highlightBlock(block);
			});
		};

		soramame.execCodeBlock = function() {
			var codeText = getCodeBlock();
			soramame.exec = new Function(codeText);
			soramame.exec();
		};

		/** Express Line Editor for SoraMame.Block =============
			Using Modal.js of bootstrap
		 */
		var openExpDialog = function(expBody) {
			$('#expModalDialog').modal();
			var textArea = $('#expModalText');
			textArea.attr("size",(expBody.length < 10)? 10 : expBody.length * 2);
			textArea.val(expBody);
		};

		$('span.exp-body').click(function() {
			expDialog_hundle = $(this);
			openExpDialog(expDialog_hundle.text());
		})

		/** When open dialog, focus on textbox for bootstrap3 */
		$('#expModalDialog').on('shown.bs.modal', function () {
			$('#expModalText').focus();
		});

		$('#btn_ok_expModalDialog').click(function() {
			var strTextBox = $('#expModalText').val();
			expDialog_hundle.text(strTextBox);
			var itemName = expDialog_hundle.attr('class').split(" ")[1];
			expDialog_hundle.parent().next().find('span.' + itemName).text(strTextBox)
			$('#expModalDialog').modal('hide');
		});

		/** add Single Global var. */
		if (typeof window.SORAMAME_BLOCK == "undefined") {
			window.SORAMAME_BLOCK = soramame;
		}

		/** connect for SoraMame.Block and jquery-sortable =============
		 */
		$('ol.pallet-code').sortable({
			group: 'connect-area',
			drop: false,
			onDragStart: function (item, container, _super) {
				// Duplicate items of the no drop area
				if(!container.options.drop) {
					item.clone(true).insertAfter(item)
				}
				_super(item);
			},
		});

		$('ol.block').sortable({
			group: 'connect-area',
		});

		$('ol.trash-code').sortable({
			group: 'connect-area',
		});

	})()

	SORAMAME_BLOCK.app = {
		msg: "Hello SoraMame Block"

	};<|endoftext|>


<commit_before>JustifiedColumns = (function(window) {

    var DEFAULT_THROTTLE = 25,
        WRAPPER_CLASS = 'justified-columns-wrapper',
        NEEDS_WRAPPER = document.documentElement.style.objectFit === undefined;

    function pxAsNum(px) {
        return 1 * px.split('px')[0];
    }

    function throttle(fn, threshhold, scope) {
        threshhold || (threshhold = 250);
        var last,
            deferTimer;

        return function() {
            var context = scope || this;

            var now = +new Date,
                args = arguments;

            if (last && now < last + threshhold) {
                clearTimeout(deferTimer);
                deferTimer = setTimeout(function() {
                    last = now;
                    fn.apply(context, args);
                }, threshhold);
            } else {
                last = now;
                fn.apply(context, args);
            }
        };
    }

    function Justifier(config) {
        this.setGrid(config.grid || config);
        this.stretchSelector = config.stretch || 'img';
        this.shrink = config.shrink || false; //or 'shrink' (column-justify);
        this.autoResize = config.hasOwnProperty('autoResize') ? config.autoResize : true;
        this.grid.Justifier = this;
        this.boundReset = config.throttle === 0 ? this.reset.bind(this) : throttle(this.reset.bind(this), config.throttle || DEFAULT_THROTTLE);
        this.boundJustify = config.throttle === 0 ? this.justify.bind(this) : throttle(this.justify.bind(this), config.throttle || DEFAULT_THROTTLE);
        this.enable();
    }

    Justifier.prototype.setGrid = function(grid) {
        if (typeof grid === 'string') {
            this.grid = document.querySelector(grid);
        } else {
            this.grid = grid.jquery ? grid[0] : grid;
        }
    };

    Justifier.prototype.justify = function() {
        var self = this,
            refColComparator = Math[this.shrink ? 'min' : 'max'],
            columns = [],
            currentColumn = -1,
            referenceColumn = this.shrink ? Number.POSITIVE_INFINITY : 0,
            currentOffset;

        //sort the grid into columns based on offsetLeft; cache heights
        [].slice.call(this.grid.children).forEach(function(gridItem) {
            var gridItemStyle = window.getComputedStyle(gridItem);
            var gridItemOffsetLeft = gridItem.offsetLeft; //saves like 40% of execution time to cache this
            if (gridItemOffsetLeft !== currentOffset) {
                if (currentColumn !== -1) referenceColumn = refColComparator(columns[currentColumn].height, referenceColumn);
                currentColumn++;
                currentOffset = gridItemOffsetLeft;
                columns.push({
                    items: [],
                    height: pxAsNum(gridItemStyle.getPropertyValue('margin-top')),
                    totalImagesHeight: 0
                });
            }
            gridItem.stretchable = gridItem.querySelector(self.stretchSelector);
            if (gridItem.stretchable) {
                gridItem.stretchableHeight = gridItem.stretchable.offsetHeight;
            }
            columns[currentColumn].items.push(gridItem);
            columns[currentColumn].height += (gridItem.offsetHeight + pxAsNum(gridItemStyle.getPropertyValue('margin-bottom')));
            columns[currentColumn].totalImagesHeight += (gridItem.stretchableHeight ? gridItem.stretchableHeight : 0);
        });
        referenceColumn = refColComparator(columns[currentColumn].height, referenceColumn); //last column could be reference, too

        //modify height of "stretchable" items (if present) in each of the non-reference-columns' items
        columns.forEach(function(column, colIndex) {
            if (column.height < referenceColumn || (self.shrink && column.height > referenceColumn)) {
                var columnDelta = referenceColumn - column.height;
                var stretchedImagesDelta = 0;
                column.items.forEach(function(gridItem, itemIndex) {
                    var stretchable = gridItem.querySelector(self.stretchSelector);
                    if (stretchable) {
                        var stretchablePercent = columnDelta / column.totalImagesHeight,
                            stretchableDelta = gridItem.stretchableHeight * stretchablePercent,
                            newHeight = gridItem.stretchableHeight + stretchableDelta,
                            heightProperty = (self.shrink ? 'max' : 'min') + 'Height';
                        if (stretchable.nodeName !== 'IMG') {
                            // not an image, so just set max/min-height and call it a day
                            stretchable.style[heightProperty] = newHeight + 'px';
                        } else if (!NEEDS_WRAPPER) {
                            // can rely on objectFit, so set the max/min-height, then use objectFit to handle resize + overflow
                            stretchable.style[heightProperty] = newHeight + 'px';
                            stretchable.style.objectFit = 'cover';
                            stretchable.style.objectPosition = 'center';
                        } else {
                            //it's an IMG and we're in older IE, ugh
                            //create overflow wrapper, set wrapper max/min-height, position within wrapper
                            if (stretchable.parentNode.className !== WRAPPER_CLASS) {
                                var wrapper = document.createElement('DIV');
                                wrapper.className = WRAPPER_CLASS;
                                wrapper.style.overflow = 'hidden';
                                wrapper.style[heightProperty] = newHeight + 'px';
                                stretchable.parentNode.replaceChild(wrapper, stretchable);
                            }
                            wrapper.appendChild(stretchable);
                            if (self.shrink) {
                                var sOffset = 0.5 * (newHeight - gridItem.stretchableHeight);
                                stretchable.style.marginTop = sOffset + 'px';
                                stretchable.style.marginBottom = -1 * sOffset + 'px';
                            } else {
                                var sWidth = stretchable.offsetWidth,
                                    newWidth = sWidth * (newHeight / gridItem.stretchableHeight);
                                stretchable.style.minHeight = newHeight + 'px';
                                stretchable.style.minWidth = newWidth + 'px';
                                stretchable.style.marginLeft = -0.5 * (newWidth - sWidth) + 'px';
                            }
                        }
                        stretchedImagesDelta += stretchableDelta;
                    }
                });
                column.height += stretchedImagesDelta;
                column.totalImagesHeight += stretchedImagesDelta;
            }
        });
    };

    Justifier.prototype.reset = function() {
        [].slice.call(this.grid.querySelectorAll(this.stretchSelector)).forEach(function(s) {
            s.style.maxHeight = '';
            s.style.minHeight = '';
            if (NEEDS_WRAPPER) {
                s.style.minWidth = '';
                s.style.marginLeft = '';
                s.style.marginTop = '';
            } else {
                s.style.objectFit = '';
                s.style.objectPosition = '';
            }
        });
        if (NEEDS_WRAPPER) {
            [].slice.call(this.grid.getElementsByClassName(WRAPPER_CLASS)).forEach(function(wrapper) {
                wrapper.parentNode.replaceChild(wrapper.firstChild, wrapper);
            });
        }
    };

    Justifier.prototype.enable = function() {
        if (this.autoResize) {
            window.addEventListener('resize', this.boundReset);
            window.addEventListener('resize', this.boundJustify);
        }
        this.justify();
    };

    Justifier.prototype.disable = function() {
        if (this.autoResize) {
            window.removeEventListener('resize', this.boundReset);
            window.removeEventListener('resize', this.boundJustify);
        }
        this.reset();
    };

    return Justifier;

})(window);<commit_msg>Workaround for IE10/11 adding unnecessary height to the "grid" object (anything using column-count) when child elements have height/min-height/max-height<commit_after>JustifiedColumns = (function(window) {

    var DEFAULT_THROTTLE = 25,
        WRAPPER_CLASS = 'justified-columns-wrapper',
        NEEDS_WRAPPER = document.documentElement.style.objectFit === undefined;

    function pxAsNum(px) {
        return 1 * px.split('px')[0];
    }

    function throttle(fn, threshhold, scope) {
        threshhold || (threshhold = 250);
        var last,
            deferTimer;

        return function() {
            var context = scope || this;

            var now = +new Date,
                args = arguments;

            if (last && now < last + threshhold) {
                clearTimeout(deferTimer);
                deferTimer = setTimeout(function() {
                    last = now;
                    fn.apply(context, args);
                }, threshhold);
            } else {
                last = now;
                fn.apply(context, args);
            }
        };
    }

    function Justifier(config) {
        this.setGrid(config.grid || config);
        this.stretchSelector = config.stretch || 'img';
        this.shrink = config.shrink || false; //or 'shrink' (column-justify);
        this.autoResize = config.hasOwnProperty('autoResize') ? config.autoResize : true;
        this.grid.Justifier = this;
        this.boundReset = config.throttle === 0 ? this.reset.bind(this) : throttle(this.reset.bind(this), config.throttle || DEFAULT_THROTTLE);
        this.boundJustify = config.throttle === 0 ? this.justify.bind(this) : throttle(this.justify.bind(this), config.throttle || DEFAULT_THROTTLE);
        this.enable();
    }

    Justifier.prototype.setGrid = function(grid) {
        if (typeof grid === 'string') {
            this.grid = document.querySelector(grid);
        } else {
            this.grid = grid.jquery ? grid[0] : grid;
        }
    };

    Justifier.prototype.justify = function() {
        var self = this,
            refColComparator = Math[this.shrink ? 'min' : 'max'],
            columns = [],
            currentColumn = -1,
            referenceColumn = this.shrink ? Number.POSITIVE_INFINITY : 0,
            currentOffset;

        //sort the grid into columns based on offsetLeft; cache heights
        [].slice.call(this.grid.children).forEach(function(gridItem) {
            var gridItemStyle = window.getComputedStyle(gridItem);
            var gridItemOffsetLeft = gridItem.offsetLeft; //saves like 40% of execution time to cache this
            if (gridItemOffsetLeft !== currentOffset) {
                if (currentColumn !== -1) referenceColumn = refColComparator(columns[currentColumn].height, referenceColumn);
                currentColumn++;
                currentOffset = gridItemOffsetLeft;
                columns.push({
                    items: [],
                    height: pxAsNum(gridItemStyle.getPropertyValue('margin-top')),
                    totalImagesHeight: 0
                });
            }
            gridItem.stretchable = gridItem.querySelector(self.stretchSelector);
            if (gridItem.stretchable) {
                gridItem.stretchableHeight = gridItem.stretchable.offsetHeight;
            }
            columns[currentColumn].items.push(gridItem);
            columns[currentColumn].height += (gridItem.offsetHeight + pxAsNum(gridItemStyle.getPropertyValue('margin-bottom')));
            columns[currentColumn].totalImagesHeight += (gridItem.stretchableHeight ? gridItem.stretchableHeight : 0);
        });
        referenceColumn = refColComparator(columns[currentColumn].height, referenceColumn); //last column could be reference, too

        //modify height of "stretchable" items (if present) in each of the non-reference-columns' items
        columns.forEach(function(column, colIndex) {
            if (column.height < referenceColumn || (self.shrink && column.height > referenceColumn)) {
                var columnDelta = referenceColumn - column.height;
                var stretchedImagesDelta = 0;
                column.items.forEach(function(gridItem, itemIndex) {
                    var stretchable = gridItem.querySelector(self.stretchSelector);
                    if (stretchable) {
                        var stretchablePercent = columnDelta / column.totalImagesHeight,
                            stretchableDelta = gridItem.stretchableHeight * stretchablePercent,
                            newHeight = gridItem.stretchableHeight + stretchableDelta,
                            heightProperty = (self.shrink ? 'max' : 'min') + 'Height';
                        if (stretchable.nodeName !== 'IMG') {
                            // not an image, so just set max/min-height and call it a day
                            stretchable.style[heightProperty] = newHeight + 'px';
                        } else if (!NEEDS_WRAPPER) {
                            // can rely on objectFit, so set the max/min-height, then use objectFit to handle resize + overflow
                            stretchable.style[heightProperty] = newHeight + 'px';
                            stretchable.style.objectFit = 'cover';
                            stretchable.style.objectPosition = 'center';
                        } else {
                            //it's an IMG and we're in older IE, ugh
                            if (stretchable.parentNode.className !== WRAPPER_CLASS) {
                                //create overflow wrapper
                                var wrapper = document.createElement('DIV');
                                wrapper.className = WRAPPER_CLASS;
                                wrapper.style.overflow = 'hidden';
                                wrapper.style[heightProperty] = newHeight + 'px';
                                stretchable.parentNode.replaceChild(wrapper, stretchable);
                                wrapper.appendChild(stretchable);
                            }
                            //set wrapper max/min-height, position within wrapper
                            if (self.shrink) {
                                var sOffset = 0.5 * (newHeight - gridItem.stretchableHeight);
                                stretchable.style.marginTop = sOffset + 'px';
                                stretchable.style.marginBottom = -1 * sOffset + 'px';
                            } else {
                                var sWidth = stretchable.offsetWidth,
                                    newWidth = sWidth * (newHeight / gridItem.stretchableHeight);
                                stretchable.style.minHeight = newHeight + 'px';
                                stretchable.style.minWidth = newWidth + 'px';
                                stretchable.style.marginLeft = -0.5 * (newWidth - sWidth) + 'px';
                            }
                        }
                        stretchedImagesDelta += stretchableDelta;
                    }
                });
                column.height += stretchedImagesDelta;
                column.totalImagesHeight += stretchedImagesDelta;
            }
        });

        //Fix the stupid IE10/11 interactions between column-count grid and height/max-height children
        if (NEEDS_WRAPPER && abs(this.grid.offsetHeight - referenceColumn) >= 2) {
            this.grid.style.height = referenceColumn + 'px';
        }

    };

    Justifier.prototype.reset = function() {
        [].slice.call(this.grid.querySelectorAll(this.stretchSelector)).forEach(function(s) {
            s.style.maxHeight = '';
            s.style.minHeight = '';
            if (NEEDS_WRAPPER) {
                s.style.minWidth = '';
                s.style.marginLeft = '';
                s.style.marginTop = '';
            } else {
                s.style.objectFit = '';
                s.style.objectPosition = '';
            }
        });
        if (NEEDS_WRAPPER) {
            [].slice.call(this.grid.getElementsByClassName(WRAPPER_CLASS)).forEach(function(wrapper) {
                wrapper.parentNode.replaceChild(wrapper.firstChild, wrapper);
            });
            this.grid.style.height = '';
        }
    };

    Justifier.prototype.enable = function() {
        if (this.autoResize) {
            window.addEventListener('resize', this.boundReset);
            window.addEventListener('resize', this.boundJustify);
        }
        this.justify();
    };

    Justifier.prototype.disable = function() {
        if (this.autoResize) {
            window.removeEventListener('resize', this.boundReset);
            window.removeEventListener('resize', this.boundJustify);
        }
        this.reset();
    };

    return Justifier;

})(window);<|endoftext|>


<commit_before>// Browser action clicked
chrome.browserAction.onClicked.addListener(function(tab)
{
    // Get enabled flag
    chrome.storage.local.get('enabled', function(result)
    {
		// First time?
		if ( result.enabled == undefined )
		{
			result.enabled = true;
		}

        // Toggle enabled status
        var enabled = !result.enabled;

        // Save it using the Chrome extension storage API.
        chrome.storage.local.set({'enabled': enabled});

        // Refresh extension button
        refreshIcon(tab, enabled);
    });

    // Refresh extension button
    refreshIcon(tab);
});

// Refresh the browser action icon based on enabled/disabled state
function refreshIcon(tab, enabled)
{
    // Get enabled flag
    chrome.storage.local.get('enabled', function(result)
    {
        // Extension enabled?
        if (result.enabled || result.enabled == undefined)
        {
            chrome.browserAction.setIcon({path: "assets/img/icon48.png", tabId: tab.id});
        }
        else
        {
            chrome.browserAction.setIcon({path: "assets/img/icon48_off.png", tabId: tab.id});
        }
    });
}

// Listen for tab update event
chrome.tabs.onUpdated.addListener(function(tabId , info)
{
    refreshIcon({ id: tabId });
});
<commit_msg>Reformat background.js code<commit_after>// Browser action clicked
chrome.browserAction.onClicked.addListener(function (tab) {
    // Get enabled flag
    chrome.storage.local.get('enabled', function (result) {
        // First time?
        if (result.enabled == undefined) {
            result.enabled = true;
        }

        // Toggle enabled status
        var enabled = !result.enabled;

        // Save it using the Chrome extension storage API.
        chrome.storage.local.set({ 'enabled': enabled });

        // Refresh extension button
        refreshIcon(tab, enabled);
    });

    // Refresh extension button
    refreshIcon(tab);
});

// Refresh the browser action icon based on enabled/disabled state
function refreshIcon(tab, enabled) {
    // Get enabled flag
    chrome.storage.local.get('enabled', function (result) {
        // Extension enabled?
        if (result.enabled || result.enabled == undefined) {
            chrome.browserAction.setIcon({ path: "assets/img/icon48.png", tabId: tab.id });
        }
        else {
            // Extension disabled
            chrome.browserAction.setIcon({ path: "assets/img/icon48_off.png", tabId: tab.id });
        }
    });
}

// Listen for tab update event
chrome.tabs.onUpdated.addListener(function (tabId, info) {
    refreshIcon({ id: tabId });
});
<|endoftext|>


<commit_before>var layoutEngine = require('./layout/forceDirectedLayout');
var parse = require('./parse/parse');

var log = function(message) {
  console.log(message);

  var li = document.createElement('li');
  li.innerHTML = message;
  document.getElementById('output').appendChild(li);
};

var StopWatchLogger = function() {};
StopWatchLogger.prototype = {
  start: function(name) {
    log('' + name + ' started');
    this.name = name;
    this.startTime = Date.now();
  },

  stop: function() {
    log('' + this.name + ' completed [' + (Date.now() - this.startTime) + 'ms]');
  }
};

function processGcl(gcl) {
  var stopwatch = new StopWatchLogger();

  stopwatch.start('Parsing');
  var parseResult = parse(gcl);
  stopwatch.stop();

  var errors = parseResult.gclErrors;
  console.log(errors);

  var model = parseResult.model;

  stopwatch.start('Layout engine');
  layoutEngine(model);
  stopwatch.stop();
  console.debug(model);
}

gclSubmit = function() {
  document.getElementById('output').innerHTML = '';

  try {
    var gcl = document.forms.gcl['gcl-code'].value;
    processGcl(gcl);
  } catch (e) {
    log('Failed');
    console.error(e);
  }

  return false;
};
<commit_msg>Don't warn about global event handler<commit_after>var layoutEngine = require('./layout/forceDirectedLayout');
var parse = require('./parse/parse');

var log = function(message) {
  console.log(message);

  var li = document.createElement('li');
  li.innerHTML = message;
  document.getElementById('output').appendChild(li);
};

var StopWatchLogger = function() {};
StopWatchLogger.prototype = {
  start: function(name) {
    log('' + name + ' started');
    this.name = name;
    this.startTime = Date.now();
  },

  stop: function() {
    log('' + this.name + ' completed [' + (Date.now() - this.startTime) + 'ms]');
  }
};

function processGcl(gcl) {
  var stopwatch = new StopWatchLogger();

  stopwatch.start('Parsing');
  var parseResult = parse(gcl);
  stopwatch.stop();

  var errors = parseResult.gclErrors;
  console.log(errors);

  var model = parseResult.model;

  stopwatch.start('Layout engine');
  layoutEngine(model);
  stopwatch.stop();
  console.debug(model);
}

/*eslint-disable*/
gclSubmit = function() {
  document.getElementById('output').innerHTML = '';

  try {
    var gcl = document.forms.gcl['gcl-code'].value;
    processGcl(gcl);
  } catch (e) {
    log('Failed');
    console.error(e);
  }

  return false;
};
/*eslint-enable*/
<|endoftext|>


<commit_before>(function(window) {
  function findValueWithName(array, name) {
    var value;

    array.forEach(function(object) {
      if (object.name === name) {
        value = object.value;
      }
    });

    return value;
  }

  function trackNewcomerSignupDone() {
    if ($('.js-newcomer-register-submit-thanks').length > 0) {
      analytics.track('Newcomer finished Wintrgarden signup', {});
    }
  }

  function trackNewcomerSignupStart() {
    var trackNewcomer = function(selector, regType) {
     $(document).on('click', selector, function() {
       analytics.track('Newcomer start Wintrgarden signup', {
         reg_type: regType,
         signup_url: location.href
       });
     });
    };

    trackNewcomer('.js-wintrgarden-signup-no-reg', 'No reg.');
    trackNewcomer('.js-wintrgarden-signup-reg', 'Reg.');
  }

  function trackCompanySignup() {
    $('.js-company-signup-form').submit(function() {
      var formData = $(this).serializeArray();

      var email = findValueWithName(formData, 'email');
      var phone = findValueWithName(formData, 'phone');
      var name = findValueWithName(formData, 'last_name');
      var company = findValueWithName(formData, 'company');
      var city = findValueWithName(formData, '00N580000088gDR');

      analytics.track('Company Interest Sign Up', {
        name: name,
        email: email,
        phone: phone,
        company: company,
        city: city,
        signup_url: location.href
      });
    });
  }

  function trackNewcomerRegPopupOpen() {
    $('.cd-popup-trigger-newcomer-signup').on('click', function() {
      analytics.track('Newcomer reg popup open');
    });
  }

  function trackCompanyRegPopupOpen() {
    $('.cd-popup-trigger-signin').on('click', function() {
      analytics.track('Company reg popup open');
    });
  }

  function initTrackOfDOMElements() {
    $('[data-track]').each(function() {
      var $element = $(this);
      var eventName = $element.attr('data-track') || 'click';
      var label = $element.attr('data-track-label');

      if (label) {
        // Add event listener to element
        $(document).on(eventName, function() {
          analytics.track(label, {});
        });
      } else {
        console.error('[just-track] data-track-label can *not* be blank!')
      }
    });
  }

  var JustTrack = {
    init: function() {
      trackCompanySignup();
      trackNewcomerSignupStart();
      trackNewcomerSignupDone();
      trackNewcomerRegPopupOpen();
      trackCompanyRegPopupOpen();
      initTrackOfDOMElements();
    }
  };

  window.JustTrack = JustTrack;
})(window);
<commit_msg>Add current locale to analytics. Closes #64<commit_after>(function(window) {
  function findValueWithName(array, name) {
    var value;

    array.forEach(function(object) {
      if (object.name === name) {
        value = object.value;
      }
    });

    return value;
  }

  function trackNewcomerSignupDone() {
    if ($('.js-newcomer-register-submit-thanks').length > 0) {
      analytics.track('Newcomer finished Wintrgarden signup', {
        locale: CURRENT_LOCALE
      });
    }
  }

  function trackNewcomerSignupStart() {
    var trackNewcomer = function(selector, regType) {
     $(document).on('click', selector, function() {
       analytics.track('Newcomer start Wintrgarden signup', {
         reg_type: regType,
         signup_url: location.href,
         locale: CURRENT_LOCALE
       });
     });
    };

    trackNewcomer('.js-wintrgarden-signup-no-reg', 'No reg.');
    trackNewcomer('.js-wintrgarden-signup-reg', 'Reg.');
  }

  function trackCompanySignup() {
    $('.js-company-signup-form').submit(function() {
      var formData = $(this).serializeArray();

      var email = findValueWithName(formData, 'email');
      var phone = findValueWithName(formData, 'phone');
      var name = findValueWithName(formData, 'last_name');
      var company = findValueWithName(formData, 'company');
      var city = findValueWithName(formData, '00N580000088gDR');

      analytics.track('Company Interest Sign Up', {
        name: name,
        email: email,
        phone: phone,
        company: company,
        city: city,
        signup_url: location.href,
        locale: CURRENT_LOCALE
      });
    });
  }

  function trackNewcomerRegPopupOpen() {
    $('.cd-popup-trigger-newcomer-signup').on('click', function() {
      analytics.track('Newcomer reg popup open', {
        locale: CURRENT_LOCALE
      });
    });
  }

  function trackCompanyRegPopupOpen() {
    $('.cd-popup-trigger-signin').on('click', function() {
      analytics.track('Company reg popup open', {
        locale: CURRENT_LOCALE
      });
    });
  }

  function initTrackOfDOMElements() {
    $('[data-track]').each(function() {
      var $element = $(this);
      var eventName = $element.attr('data-track') || 'click';
      var label = $element.attr('data-track-label');

      if (label) {
        // Add event listener to element
        $(document).on(eventName, function() {
          analytics.track(label, {
            locale: CURRENT_LOCALE
          });
        });
      } else {
        console.error('[just-track] data-track-label can *not* be blank!')
      }
    });
  }

  var JustTrack = {
    init: function() {
      trackCompanySignup();
      trackNewcomerSignupStart();
      trackNewcomerSignupDone();
      trackNewcomerRegPopupOpen();
      trackCompanyRegPopupOpen();
      initTrackOfDOMElements();
    }
  };

  window.JustTrack = JustTrack;
})(window);
<|endoftext|>


<commit_before>// @flow
import firebase from '@firebase/app'
import '@firebase/firestore'
import CommandParser from '../utils/commandParser'

const commandParser = new CommandParser()

export const getMessagesByGameId = async (id: string, _first: number) => {
  const collection = firebase
    .firestore()
    .collection(`messages`)
    .where('game', '==', id)
    .orderBy('timestamp', 'desc')
    .limit(20)

  const docs = await collection.get()

  const messages = []
  docs.forEach(doc =>
    messages.push({
      id: doc.id,
      ...doc.data()
    })
  )

  // Most recent messages arrive in order from latest to oldest, we reverse for
  // the frontend
  const reversed: Array<Object> = messages.reverse()
  return reversed
}

export const listenToNewMessages = (id: string) => (callback: Function) => {
  const query = firebase
    .firestore()
    .collection(`messages`)
    .where('game', '==', id)
    .orderBy('timestamp', 'desc')
    .limit(20)

  return Promise.resolve(
    query.onSnapshot(
      snapshot => {
        snapshot.docChanges.forEach(change => {
          if (change.type === 'added') {
            const message = {
              id: change.doc.id,
              ...change.doc.data({ serverTimestamps: 'estimate' })
            }
            callback(message)
          } else if (change.type === 'modified') {
          } else if (change.type === 'removed') {
          }
        })
      },
      error => {
        console.log('error in firebase sub', error)
      }
    )
  )
}

export const sendMessage = (id: string, text: string) => {
  const messagesCollection = firebase.firestore().collection(`messages`)

  const result = commandParser.getMessageResult(text)

  const message = {
    from: firebase.auth().currentUser.displayName,
    result,
    text,
    game: id,
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }

  messagesCollection.add(message)

  return {
    id: -1,
    ...message,
    timestamp: new Date()
  }
}
<commit_msg>Send messages using user id.<commit_after>// @flow
import firebase from '@firebase/app'
import '@firebase/firestore'
import CommandParser from '../utils/commandParser'

const commandParser = new CommandParser()

export const getMessagesByGameId = async (id: string, _first: number) => {
  const collection = firebase
    .firestore()
    .collection(`messages`)
    .where('game', '==', id)
    .orderBy('timestamp', 'desc')
    .limit(20)

  const docs = await collection.get()

  const messages = []
  docs.forEach(doc =>
    messages.push({
      id: doc.id,
      ...doc.data()
    })
  )

  // Most recent messages arrive in order from latest to oldest, we reverse for
  // the frontend
  const reversed: Array<Object> = messages.reverse()
  return reversed
}

export const listenToNewMessages = (id: string) => (callback: Function) => {
  const query = firebase
    .firestore()
    .collection(`messages`)
    .where('game', '==', id)
    .orderBy('timestamp', 'desc')
    .limit(20)

  return Promise.resolve(
    query.onSnapshot(
      snapshot => {
        snapshot.docChanges.forEach(change => {
          if (change.type === 'added') {
            const message = {
              id: change.doc.id,
              ...change.doc.data({ serverTimestamps: 'estimate' })
            }
            callback(message)
          } else if (change.type === 'modified') {
          } else if (change.type === 'removed') {
          }
        })
      },
      error => {
        console.log('error in firebase sub', error)
      }
    )
  )
}

export const sendMessage = (id: string, text: string) => {
  const messagesCollection = firebase.firestore().collection(`messages`)

  const result = commandParser.getMessageResult(text)

  const message = {
    from: firebase.auth().currentUser.uid,
    result,
    text,
    game: id,
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
  }

  messagesCollection.add(message)

  return {
    id: -1,
    ...message,
    timestamp: new Date()
  }
}
<|endoftext|>


<commit_before>export default {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "definitions": {
        "variable": {
            "type": "object",
            "properties": {
                "symbol": {
                    "type": "string"
                },
                "sensorId": {
                    "type": "string"
                },
                "measurementType": {
                    "type": "string"
                },
                "required": [
                    "symbol",
                    "sensorId",
                    "measurementType"
                ],
                "additionalProperties": false
            }
        },
        "formula": {
            "type": "object",
            "properties": {
                "formula": {
                    "type": "string"
                },
                "variables": {
                    "type": "array",
                    "uniqueItems": true,
                    "items": {
                        "$ref": "#/definitions/variable"
                    }
                },
                "start": {
                    "type": "string",
                    "format": "date-time"
                },
                "end": {
                    "type": "string",
                    "format": "date-time"
                },
                "measurementType": {
                    "type": "string"
                },
                "measurementUnit": {
                    "type": "string"
                },
                "measurementSample": {
                    "type": "integer",
                    "minimum": 60000,
                    "maximum": 21600000
                },
                "aggregationType": {
                    "type": "string",
                    "enum": [
                        "average",
                        "difference",
                        "max",
                        "min",
                        "newest",
                        "oldest",
                        "sum"
                    ]
                }
            },
            "required": [
                "formula",
                "variables",
                "start",
                "end",
                "measurementType",
                "measurementUnit",
                "measurementSample"
            ],
            "additionalProperties": false
        },
        "measurementInfo": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string"
                },
                "tags": {
                    "type": "array",
                    "minItems": 1,
                    "uniqueItems": true,
                    "items": {
                        "type": "string"
                    }
                },
                "primaryTags": {
                    "type": "array",
                    "minItems": 1,
                    "uniqueItems": true,
                    "items": {
                        "type": "string"
                    }
                },
                "description": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "unitOfMeasurement": {
                    "type": "string"
                }
            },
            "required": ["type"],
            "anyOf": [{
                "required": ["tags"]
            }, {
                "required": ["primaryTags"]
            }, {
                "required": ["description"]
            }, {
                "required": ["name"]
            }, {
                "required": ["unitOfMeasurement"]
            }],
            "additionalProperties": false
        }
    },
    "type": "object",
    "properties": {
        "id": {
            "type": "string"
        },
        "name": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "shortDescription": {
            "type": "string"
        },
        "virtual": {
            "type": "boolean"
        },
        "formulas": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/formula"
            }
        },
        "tags": {
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        "primaryTags": {
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        "measurementsInfo": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/measurementInfo"
            }
        },
        "siteReference": {
            "type": "string"
        },
        "userReference": {
            "type": "string"
        },
        "userId": {
            "type": "string"
        },
        "parentSensorId": {
            "type": "string"
        },
        "unitOfMeasurement": {
            "type": "string"
        },
        "aggregationType": {
            "type": "string",
            "enum": [
                "average",
                "difference",
                "max",
                "min",
                "newest",
                "oldest",
                "sum"
            ]
        }
    },
    "required": [
        "id",
        "name"
    ],
    "additionalProperties": false
};
<commit_msg>Correct variable def<commit_after>export default {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "definitions": {
        "variable": {
            "type": "object",
            "properties": {
                "symbol": {
                    "type": "string"
                },
                "sensorId": {
                    "type": "string"
                },
                "measurementType": {
                    "type": "string"
                }
            },
            "required": [
                "symbol",
                "sensorId",
                "measurementType"
            ],
            "additionalProperties": false
        },
        "formula": {
            "type": "object",
            "properties": {
                "formula": {
                    "type": "string"
                },
                "variables": {
                    "type": "array",
                    "uniqueItems": true,
                    "items": {
                        "$ref": "#/definitions/variable"
                    }
                },
                "start": {
                    "type": "string",
                    "format": "date-time"
                },
                "end": {
                    "type": "string",
                    "format": "date-time"
                },
                "measurementType": {
                    "type": "string"
                },
                "measurementUnit": {
                    "type": "string"
                },
                "measurementSample": {
                    "type": "integer",
                    "minimum": 60000,
                    "maximum": 21600000
                },
                "aggregationType": {
                    "type": "string",
                    "enum": [
                        "average",
                        "difference",
                        "max",
                        "min",
                        "newest",
                        "oldest",
                        "sum"
                    ]
                }
            },
            "required": [
                "formula",
                "variables",
                "start",
                "end",
                "measurementType",
                "measurementUnit",
                "measurementSample"
            ],
            "additionalProperties": false
        },
        "measurementInfo": {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string"
                },
                "tags": {
                    "type": "array",
                    "minItems": 1,
                    "uniqueItems": true,
                    "items": {
                        "type": "string"
                    }
                },
                "primaryTags": {
                    "type": "array",
                    "minItems": 1,
                    "uniqueItems": true,
                    "items": {
                        "type": "string"
                    }
                },
                "description": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                },
                "unitOfMeasurement": {
                    "type": "string"
                }
            },
            "required": ["type"],
            "anyOf": [{
                "required": ["tags"]
            }, {
                "required": ["primaryTags"]
            }, {
                "required": ["description"]
            }, {
                "required": ["name"]
            }, {
                "required": ["unitOfMeasurement"]
            }],
            "additionalProperties": false
        }
    },
    "type": "object",
    "properties": {
        "id": {
            "type": "string"
        },
        "name": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "shortDescription": {
            "type": "string"
        },
        "virtual": {
            "type": "boolean"
        },
        "formulas": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/formula"
            }
        },
        "tags": {
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        "primaryTags": {
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        "measurementsInfo": {
            "type": "array",
            "items": {
                "$ref": "#/definitions/measurementInfo"
            }
        },
        "siteReference": {
            "type": "string"
        },
        "userReference": {
            "type": "string"
        },
        "userId": {
            "type": "string"
        },
        "parentSensorId": {
            "type": "string"
        },
        "unitOfMeasurement": {
            "type": "string"
        },
        "aggregationType": {
            "type": "string",
            "enum": [
                "average",
                "difference",
                "max",
                "min",
                "newest",
                "oldest",
                "sum"
            ]
        }
    },
    "required": [
        "id",
        "name"
    ],
    "additionalProperties": false
};
<|endoftext|>


<commit_before>/***
 * Recursively encode an object as application/x-www-form-urlencoded.
 *
 * @param value Value to encode
 * @param key Key to encode (not required for top-level objects)
 * @return Encoded object
 */
var serialize = module.exports.serialize = function (value, key) {

	var output;
    key || (key = '');

    if (Array.isArray(value)) {
        output = [];
        value.forEach(function(val, index) {
            if (key != '') index = key + '[' + index + ']';
            output.push(serialize(val, index));
        }, this);
        return output.join('&');
    } else if (typeof(value) == 'object') {
        output = [];
        for (var name in value) {
            if (value[name] && value.hasOwnProperty(name)) {
                output.push(serialize(value[name], key != '' ? key + '[' + name + ']' : name));
            }
        }
        return output.join('&');
    } else {
        return key + '=' + encodeURIComponent(value);
    }

}<commit_msg>Fixed problem when key was 0<commit_after>/***
 * Recursively encode an object as application/x-www-form-urlencoded.
 *
 * @param value Value to encode
 * @param key Key to encode (not required for top-level objects)
 * @return Encoded object
 */
var serialize = module.exports.serialize = function (value, key) {

	var output;

	if (!key && key !== 0)
		key = '';

    if (Array.isArray(value)) {
        output = [];
        value.forEach(function(val, index) {
            if (key != '') index = key + '[' + index + ']';
            output.push(serialize(val, index));
        }, this);
        return output.join('&');
    } else if (typeof(value) == 'object') {
        output = [];
        for (var name in value) {
            if (value[name] && value.hasOwnProperty(name)) {
                output.push(serialize(value[name], key != '' ? key + '[' + name + ']' : name));
            }
        }
        return output.join('&');
    } else {
        return key + '=' + encodeURIComponent(value);
    }

}<|endoftext|>


<commit_before>let HelpersFactory = () => {
  return {
    safeSubtract: (what, from) => {
      return what > from ? 0 : from - what;
    },

    shuffleArray: (array) => {
      let result = array.slice(0);

      // adapted from http://stackoverflow.com/a/6274381/6696238
      for (let i = result.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        // elements swap
        [result[i - 1], result[j]] = [result[j], result[i - 1]];
      }

      return result;
    }
  };
};

export default HelpersFactory;
<commit_msg>TODO do not rely on Math.random in shuffleArray<commit_after>let HelpersFactory = () => {
  return {
    safeSubtract: (what, from) => {
      return what > from ? 0 : from - what;
    },

    shuffleArray: (array) => {
      let result = array.slice(0);

      // adapted from http://stackoverflow.com/a/6274381/6696238
      for (let i = result.length; i; i--) {
        // TODO: do not rely only on Math.random which can provide a few same results in a row
        let j = Math.floor(Math.random() * i);
        // elements swap
        [result[i - 1], result[j]] = [result[j], result[i - 1]];
      }

      return result;
    }
  };
};

export default HelpersFactory;
<|endoftext|>


<commit_before>"use strict";

// include core Park class
const Park = require("../park");
const Moment = require("moment-timezone");

// uuid generation lib
const uuid = require("uuid/v4");
// zip lib to extract data
const unzip = require("yauzl");

const s_apiKey = Symbol();
const s_apiBase = Symbol();
const s_calendarURLBase = Symbol();
const s_appVersion = Symbol();
const s_appBuild = Symbol();
const s_deviceID = Symbol();
const s_dataVersion = Symbol();

const s_deviceTokenCacheTime = Symbol();
const s_userIDCacheTime = Symbol();
const s_dataCacheTime = Symbol();

/**
 * Implements the Merlin Park API framework. Thorpe Park, Alton Towers, Chessington etc. use this API framework
 * @class
 * @extends Park
 */
class MerlinPark extends Park {
    /**
     * Create new Merlin Object.
     * This object should not be called directly, but rather extended for each of the individual Merlin parks
     * @param {Object} options
     * @param {String} options.api_key API key to access this park's API
     * @param {String} [options.api_base] API Base to use when accessing the API
     * @param {String} [options.app_build] Build version of the app (optional)
     * @param {String} [options.app_version] App version of the app (optional)
     * @param {String} [options.device_id] Device identifier for use against the API (optional)
     * @param {String} [options.initial_data_version] The initial version timestamp to fetch (optional)
     * @param {Number} [options.device_token_cachetime=86400000] Time to cache device token, in milliseconds (optional)
     * @param {Number} [options.user_id_cachetime=43200000] Time to cache user ID, in milliseconds (optional)
     * @param {Number} [options.data_cachetime=43200000] Time to ride data, in milliseconds (optional)
     */
    constructor(options = {}) {
        options.name = options.name || "Merlin Park";

        // hard-code UserAgent for these parks
        //  do this before calling super, so we don't get a randomly generated one
        options.useragent = "okhttp/3.2.0";

        // inherit from base class
        super(options);

        // custom API options
        if (!options.api_key) throw new Error("Merlin Parks require an API key");
        this[s_apiKey] = options.api_key;
        if (!options.initial_data_version) throw new Error("Merlin Parks require an initial data version to fetch ride names");
        this[s_dataVersion] = options.initial_data_version;

        // app version options (optionally overriden)
        this[s_appBuild] = options.app_build || "5";
        this[s_appVersion] = options.app_version || "1.0.1";
        this[s_deviceID] = options.device_id || "123";

        // default base API
        this[s_apiBase] = options.api_base || "https://api.attractions.io/v1/";
        this[s_calendarURLBase] = options.calendar_base || "https://www.thorpepark.com/";

        // cache times
        this[s_deviceTokenCacheTime] = options.device_token_cachetime || 86400000; // default: 24 hours
        this[s_userIDCacheTime] = options.user_id_cachetime || 43200000; // default: 12 hours
        this[s_dataCacheTime] = options.data_cachetime || 43200000; // default: 12 hours
    }

    /**
     * Get the API Base URL
     */
    get APIBase() {
        return this[s_apiBase];
    }

    FetchWaitTimes() {
        // first, make sure we have our park data (ride names etc.)
        return this.GetParkData().then((rideNames) => {
            // fetch wait times
            return this.MakeAPICall({
                url: `${this.APIBase}live-data`
            }).then((data) => {
                for (var i = 0, ride; ride = data.entities.Item.records[i++];) {
                    // apply each wait time data
                    var rideObject = this.GetRideObject({
                        id: ride._id,
                        name: rideNames[ride._id],
                    });

                    if (!rideObject) {
                        this.Log(`Failed to find ride with ID ${ride.id}`);
                    } else {
                        // update ride wait time (wait times are in seconds in this API!)
                        rideObject.WaitTime = ride.IsOpen ? (ride.QueueTime / 60) : -1;
                    }
                }

                return Promise.resolve();
            });
        });
    }

    /**
     * Get an API token from cache or through registering a new device
     */
    RegisterDevice() {
        // fetch new device token if we haven't already got one in our cache
        return this.Cache.Wrap("device_token", () => {
            // first, get (or generate) a new user ID
            return this.GenerateUserID().then((user_id) => {
                // request token for further API requests
                return this.HTTP({
                    url: `${this[s_apiBase]}installation`,
                    method: "POST",
                    data: {
                        user_identifier: user_id,
                        device_identifier: this[s_deviceID],
                        app_version: this[s_appVersion],
                        app_build: this[s_appBuild]
                    },
                    headers: {
                        "occasio-platform": "Android",
                        "occasio-platform-version": "6.0.1",
                        "occasio-app-build": this[s_appBuild],
                        "authorization": `Attractions-Io api-key "${this[s_apiKey]}"`,
                    }
                }).then((data) => {
                    if (data && data.token) {
                        return Promise.resolve(data.token);
                    }

                    return Promise.reject("No data returned");
                });
            });
        }, this[s_deviceTokenCacheTime]);
    }

    /**
     * Generate (or fetch a cached) user ID
     */
    GenerateUserID() {
        return this.Cache.Wrap("user_id", () => {
            // generate new UUID if cache hit fails
            const newUserID = uuid();

            this.Log(`Generated new UserID ${newUserID}`);

            return Promise.resolve(newUserID);
        }, this[s_userIDCacheTime]);
    }

    /**
     * Get (or fetch new) park data
     */
    GetParkData() {
        return this.Cache.Wrap("data", () => {
            // fetch fresh/updated data
            return this.FetchParkData(this.DataVersion).then((data) => {
                var rideData = {};
                for (var i = 0, item; item = data.Item[i++];) {
                    rideData[item._id] = item.Name;
                }
                return Promise.resolve(rideData);
            });
        }, this[s_dataCacheTime]);
    }

    /**
     * Get the latest data version timestamp
     */
    get DataVersion() {
        return this[s_dataVersion];
    }

    /**
     * Fetch/Sync park data
     * Warning: full sync is ~30MB
     */
    FetchParkData(version) {
        // this is a recursive function, and will keep fetching data until we get no more deltas to resolve
        //  note: we should attempt to periodically update the initialVersion to cut down on these requests

        // remember this as the latest version for next fetch
        this[s_dataVersion] = version;

        // Fetch data
        return this.MakeAPICall({
            url: `${this[s_apiBase]}data`,
            data: {
                version: version
            },
            // we want the full response to get the status code
            returnFullResponse: true,
        }).then((response) => {
            if (response.statusCode == 304) {
                // reject
                this.Log(`Reached status 304 accessing data version ${version}`);
                return Promise.reject();
            } else {
                this.Log(`Received data for version ${version}`);

                return new Promise((resolve, reject) => {
                    // unzip data
                    unzip.fromBuffer(response.body, {
                        lazyEntries: true
                    }, (err, zip) => {
                        var manifestData;
                        var recordsData;

                        this.Log("Parsing zip file");
                        if (err) {
                            return reject(err);
                        }

                        const GetNextEntry = () => {
                            if (manifestData && recordsData) {
                                // got both the files we need, stop reading the zip file

                                // fetch next data URL
                                if (manifestData.version) {
                                    this.FetchParkData(manifestData.version).catch(() => {
                                        // as soon as we hit an error, return the current level or records data
                                        return resolve(recordsData);
                                    });
                                } else {
                                    return resolve(recordsData);
                                }
                            } else {
                                // read next entry
                                zip.readEntry();
                            }
                        };

                        zip.on("entry", (file) => {
                            this.Log(`Got zip file ${file.fileName}`);

                            // look for the two files we want
                            if (file.fileName == "manifest.json") {
                                ReadZipFile(zip, file).then((data) => {
                                    manifestData = data;

                                    GetNextEntry();
                                });
                            } else if (file.fileName == "records.json") {
                                ReadZipFile(zip, file).then((data) => {
                                    recordsData = data;

                                    GetNextEntry();
                                });
                            } else {
                                GetNextEntry();
                            }
                        });

                        // start reading file...
                        zip.readEntry();
                    });
                });
            }
        });
    }

    /**
     * Generic API request function, will sort out API token and send auth headers
     * @param {*} options
     * @param {String} options.url URL to access
     * @param {String} [options.method=GET] method to use
     * @param {Object} [options.data={}] data/query string to use
     */
    MakeAPICall(options = {
        method: "GET",
        data: {}
    }) {
        // get token
        return this.RegisterDevice().then((token) => {
            // inject auth headers into request headers
            if (!options.headers) {
                options.headers = {};
            }
            options.headers["occasio-platform"] = "Android";
            options.headers["occasio-platform-version"] = "6.0.1";
            options.headers["occasio-app-build"] = this[s_appBuild];
            options.headers["authorization"] = `Attractions-Io api-key "${this[s_apiKey]}", installation-token="${token}"`;

            // make API call
            return this.HTTP(options);
        });
    }

    FetchOpeningTimes() {
        return new Promise((resolve, reject) => {
            this.HTTP({
                url: `${this[s_calendarURLBase]}Umbraco/Api/Calendar/GetAllOpeningTimes`,
                method: "GET",
                headers: {
                    "Referer": this[s_calendarURLBase],
                    "X-Requested-With": "XMLHttpRequest",
                },
                json: true
            }).then((calendarData) => {
                // find theme park dates from response
                //  it contains "WatterPark"[sic] times as well in a separate array
                var parkDates = null;
                if (calendarData[0] && calendarData[0].Type) {
                    // for resorts with multiple parks (Alton Towers)
                    for (var i = 0, times; times = calendarData[i++];) {
                        if (times.Type == "ThemePark") {
                            parkDates = times.OpeningHours;
                            break;
                        }
                    }
                } else if (calendarData[0].Open) {
                    // resorts with only 1 park (Thorpe Park)
                    parkDates = calendarData;
                } else {
                    return reject("Invalid/Unknown calendar data returned");
                }

                var result, timeRange;
                for (i = 0, timeRange; timeRange = parkDates[i++];) {
                    var range = {
                        startDate: Moment(timeRange.From, "YYYY-MM-DDTHH:mm:ss"),
                        endDate: Moment(timeRange.To, "YYYY-MM-DDTHH:mm:ss")
                    };

                    // figure out opening times for this range
                    if (result = /([0-9\:]+[ap]m)\s*\-\s*([0-9\:]+[ap]m)/gi.exec(timeRange.Open.replace(/\./g, ":"))) {
                        range.openingTime = Moment(result[1], "HH:mma");
                        range.closingTime = Moment(result[2], "HH:mma");
                    }
                    // try shorthand format too, in case someone entered the times in badly
                    else if (result = /([0-9]+)\s*\-\s*([0-9]+)/gi.exec(timeRange.Open.replace(/\./g, ":"))) {
                        range.openingTime = Moment(result[1] + ":00am", "HH:mma");
                        range.closingTime = Moment(result[2] + ":00pm", "HH:mma");
                    } else {
                        this.Log(`Unable to understand hour format: ${timeRange.Open}`);
                        continue;
                    }

                    // apply this range
                    this.Schedule.SetRange(range);
                }

                resolve();
            }, reject);
        });
    }
}

// export the class
module.exports = MerlinPark;

// static functions
function ReadZipFile(zip, file) {
    return new Promise((resolve, reject) => {
        var data = "";
        zip.openReadStream(file, function(err, readStream) {
            if (err) {
                return reject(err);
            }

            readStream.on("data", function(chunk) {
                data += chunk;
            }).on("end", function() {
                // parse JSON data
                try {
                    data = JSON.parse(data);
                    return resolve(data);
                } catch (e) {
                    return reject(`JSON parse error extracting ${file.fileName}: ${e}`);
                }
            });
        });
    });
}<commit_msg>Fix opening hours being incorrectly parsed for Thorpe Park. Updated regex to handle option space between time and am/pm, then strip out potential space before passing to Moment lib.<commit_after>"use strict";

// include core Park class
const Park = require("../park");
const Moment = require("moment-timezone");

// uuid generation lib
const uuid = require("uuid/v4");
// zip lib to extract data
const unzip = require("yauzl");

const s_apiKey = Symbol();
const s_apiBase = Symbol();
const s_calendarURLBase = Symbol();
const s_appVersion = Symbol();
const s_appBuild = Symbol();
const s_deviceID = Symbol();
const s_dataVersion = Symbol();

const s_deviceTokenCacheTime = Symbol();
const s_userIDCacheTime = Symbol();
const s_dataCacheTime = Symbol();

/**
 * Implements the Merlin Park API framework. Thorpe Park, Alton Towers, Chessington etc. use this API framework
 * @class
 * @extends Park
 */
class MerlinPark extends Park {
    /**
     * Create new Merlin Object.
     * This object should not be called directly, but rather extended for each of the individual Merlin parks
     * @param {Object} options
     * @param {String} options.api_key API key to access this park's API
     * @param {String} [options.api_base] API Base to use when accessing the API
     * @param {String} [options.app_build] Build version of the app (optional)
     * @param {String} [options.app_version] App version of the app (optional)
     * @param {String} [options.device_id] Device identifier for use against the API (optional)
     * @param {String} [options.initial_data_version] The initial version timestamp to fetch (optional)
     * @param {Number} [options.device_token_cachetime=86400000] Time to cache device token, in milliseconds (optional)
     * @param {Number} [options.user_id_cachetime=43200000] Time to cache user ID, in milliseconds (optional)
     * @param {Number} [options.data_cachetime=43200000] Time to ride data, in milliseconds (optional)
     */
    constructor(options = {}) {
        options.name = options.name || "Merlin Park";

        // hard-code UserAgent for these parks
        //  do this before calling super, so we don't get a randomly generated one
        options.useragent = "okhttp/3.2.0";

        // inherit from base class
        super(options);

        // custom API options
        if (!options.api_key) throw new Error("Merlin Parks require an API key");
        this[s_apiKey] = options.api_key;
        if (!options.initial_data_version) throw new Error("Merlin Parks require an initial data version to fetch ride names");
        this[s_dataVersion] = options.initial_data_version;

        // app version options (optionally overriden)
        this[s_appBuild] = options.app_build || "5";
        this[s_appVersion] = options.app_version || "1.0.1";
        this[s_deviceID] = options.device_id || "123";

        // default base API
        this[s_apiBase] = options.api_base || "https://api.attractions.io/v1/";
        this[s_calendarURLBase] = options.calendar_base || "https://www.thorpepark.com/";

        // cache times
        this[s_deviceTokenCacheTime] = options.device_token_cachetime || 86400000; // default: 24 hours
        this[s_userIDCacheTime] = options.user_id_cachetime || 43200000; // default: 12 hours
        this[s_dataCacheTime] = options.data_cachetime || 43200000; // default: 12 hours
    }

    /**
     * Get the API Base URL
     */
    get APIBase() {
        return this[s_apiBase];
    }

    FetchWaitTimes() {
        // first, make sure we have our park data (ride names etc.)
        return this.GetParkData().then((rideNames) => {
            // fetch wait times
            return this.MakeAPICall({
                url: `${this.APIBase}live-data`
            }).then((data) => {
                for (var i = 0, ride; ride = data.entities.Item.records[i++];) {
                    // apply each wait time data
                    var rideObject = this.GetRideObject({
                        id: ride._id,
                        name: rideNames[ride._id],
                    });

                    if (!rideObject) {
                        this.Log(`Failed to find ride with ID ${ride.id}`);
                    } else {
                        // update ride wait time (wait times are in seconds in this API!)
                        rideObject.WaitTime = ride.IsOpen ? (ride.QueueTime / 60) : -1;
                    }
                }

                return Promise.resolve();
            });
        });
    }

    /**
     * Get an API token from cache or through registering a new device
     */
    RegisterDevice() {
        // fetch new device token if we haven't already got one in our cache
        return this.Cache.Wrap("device_token", () => {
            // first, get (or generate) a new user ID
            return this.GenerateUserID().then((user_id) => {
                // request token for further API requests
                return this.HTTP({
                    url: `${this[s_apiBase]}installation`,
                    method: "POST",
                    data: {
                        user_identifier: user_id,
                        device_identifier: this[s_deviceID],
                        app_version: this[s_appVersion],
                        app_build: this[s_appBuild]
                    },
                    headers: {
                        "occasio-platform": "Android",
                        "occasio-platform-version": "6.0.1",
                        "occasio-app-build": this[s_appBuild],
                        "authorization": `Attractions-Io api-key "${this[s_apiKey]}"`,
                    }
                }).then((data) => {
                    if (data && data.token) {
                        return Promise.resolve(data.token);
                    }

                    return Promise.reject("No data returned");
                });
            });
        }, this[s_deviceTokenCacheTime]);
    }

    /**
     * Generate (or fetch a cached) user ID
     */
    GenerateUserID() {
        return this.Cache.Wrap("user_id", () => {
            // generate new UUID if cache hit fails
            const newUserID = uuid();

            this.Log(`Generated new UserID ${newUserID}`);

            return Promise.resolve(newUserID);
        }, this[s_userIDCacheTime]);
    }

    /**
     * Get (or fetch new) park data
     */
    GetParkData() {
        return this.Cache.Wrap("data", () => {
            // fetch fresh/updated data
            return this.FetchParkData(this.DataVersion).then((data) => {
                var rideData = {};
                for (var i = 0, item; item = data.Item[i++];) {
                    rideData[item._id] = item.Name;
                }
                return Promise.resolve(rideData);
            });
        }, this[s_dataCacheTime]);
    }

    /**
     * Get the latest data version timestamp
     */
    get DataVersion() {
        return this[s_dataVersion];
    }

    /**
     * Fetch/Sync park data
     * Warning: full sync is ~30MB
     */
    FetchParkData(version) {
        // this is a recursive function, and will keep fetching data until we get no more deltas to resolve
        //  note: we should attempt to periodically update the initialVersion to cut down on these requests

        // remember this as the latest version for next fetch
        this[s_dataVersion] = version;

        // Fetch data
        return this.MakeAPICall({
            url: `${this[s_apiBase]}data`,
            data: {
                version: version
            },
            // we want the full response to get the status code
            returnFullResponse: true,
        }).then((response) => {
            if (response.statusCode == 304) {
                // reject
                this.Log(`Reached status 304 accessing data version ${version}`);
                return Promise.reject();
            } else {
                this.Log(`Received data for version ${version}`);

                return new Promise((resolve, reject) => {
                    // unzip data
                    unzip.fromBuffer(response.body, {
                        lazyEntries: true
                    }, (err, zip) => {
                        var manifestData;
                        var recordsData;

                        this.Log("Parsing zip file");
                        if (err) {
                            return reject(err);
                        }

                        const GetNextEntry = () => {
                            if (manifestData && recordsData) {
                                // got both the files we need, stop reading the zip file

                                // fetch next data URL
                                if (manifestData.version) {
                                    this.FetchParkData(manifestData.version).catch(() => {
                                        // as soon as we hit an error, return the current level or records data
                                        return resolve(recordsData);
                                    });
                                } else {
                                    return resolve(recordsData);
                                }
                            } else {
                                // read next entry
                                zip.readEntry();
                            }
                        };

                        zip.on("entry", (file) => {
                            this.Log(`Got zip file ${file.fileName}`);

                            // look for the two files we want
                            if (file.fileName == "manifest.json") {
                                ReadZipFile(zip, file).then((data) => {
                                    manifestData = data;

                                    GetNextEntry();
                                });
                            } else if (file.fileName == "records.json") {
                                ReadZipFile(zip, file).then((data) => {
                                    recordsData = data;

                                    GetNextEntry();
                                });
                            } else {
                                GetNextEntry();
                            }
                        });

                        // start reading file...
                        zip.readEntry();
                    });
                });
            }
        });
    }

    /**
     * Generic API request function, will sort out API token and send auth headers
     * @param {*} options
     * @param {String} options.url URL to access
     * @param {String} [options.method=GET] method to use
     * @param {Object} [options.data={}] data/query string to use
     */
    MakeAPICall(options = {
        method: "GET",
        data: {}
    }) {
        // get token
        return this.RegisterDevice().then((token) => {
            // inject auth headers into request headers
            if (!options.headers) {
                options.headers = {};
            }
            options.headers["occasio-platform"] = "Android";
            options.headers["occasio-platform-version"] = "6.0.1";
            options.headers["occasio-app-build"] = this[s_appBuild];
            options.headers["authorization"] = `Attractions-Io api-key "${this[s_apiKey]}", installation-token="${token}"`;

            // make API call
            return this.HTTP(options);
        });
    }

    FetchOpeningTimes() {
        return new Promise((resolve, reject) => {
            this.HTTP({
                url: `${this[s_calendarURLBase]}Umbraco/Api/Calendar/GetAllOpeningTimes`,
                method: "GET",
                headers: {
                    "Referer": this[s_calendarURLBase],
                    "X-Requested-With": "XMLHttpRequest",
                },
                json: true
            }).then((calendarData) => {
                // find theme park dates from response
                //  it contains "WatterPark"[sic] times as well in a separate array
                var parkDates = null;
                if (calendarData[0] && calendarData[0].Type) {
                    // for resorts with multiple parks (Alton Towers)
                    for (var i = 0, times; times = calendarData[i++];) {
                        if (times.Type == "ThemePark") {
                            parkDates = times.OpeningHours;
                            break;
                        }
                    }
                } else if (calendarData[0].Open) {
                    // resorts with only 1 park (Thorpe Park)
                    parkDates = calendarData;
                } else {
                    return reject("Invalid/Unknown calendar data returned");
                }

                var result, timeRange;
                for (i = 0, timeRange; timeRange = parkDates[i++];) {
                    var range = {
                        startDate: Moment(timeRange.From, "YYYY-MM-DDTHH:mm:ss"),
                        endDate: Moment(timeRange.To, "YYYY-MM-DDTHH:mm:ss")
                    };

                    this.Log(`Processing ${range.startDate} => ${range.endDate}`);

                    // figure out opening times for this range
                    if (result = /([0-9\:]+\s?[ap]m)\s*\-\s*([0-9\:]+\s?[ap]m)/gi.exec(timeRange.Open.replace(/\./g, ":"))) {
                        range.openingTime = Moment(result[1].replace(/ /g, ""), "HH:mma");
                        range.closingTime = Moment(result[2].replace(/ /g, ""), "HH:mma");
                    }
                    // try shorthand format too, in case someone entered the times in badly
                    else if (result = /([0-9]+)\s*\-\s*([0-9]+)/gi.exec(timeRange.Open.replace(/\./g, ":"))) {
                        range.openingTime = Moment(result[1] + ":00am", "HH:mma");
                        range.closingTime = Moment(result[2] + ":00pm", "HH:mma");
                    } else {
                        this.Log(`Unable to understand hour format: ${timeRange.Open}`);
                        continue;
                    }

                    // apply this range
                    this.Schedule.SetRange(range);
                }

                resolve();
            }, reject);
        });
    }
}

// export the class
module.exports = MerlinPark;

// static functions
function ReadZipFile(zip, file) {
    return new Promise((resolve, reject) => {
        var data = "";
        zip.openReadStream(file, function(err, readStream) {
            if (err) {
                return reject(err);
            }

            readStream.on("data", function(chunk) {
                data += chunk;
            }).on("end", function() {
                // parse JSON data
                try {
                    data = JSON.parse(data);
                    return resolve(data);
                } catch (e) {
                    return reject(`JSON parse error extracting ${file.fileName}: ${e}`);
                }
            });
        });
    });
}<|endoftext|>


<commit_before>const home = {
	bindings: {

	},
	controller: function () {
		let ctrl = this;
		ctrl.title = 'Rowdy Mermaid';

		ctrl.$onInit = function () {
			console.log('init Home');
		};
		ctrl.$postLink = function () {};
	},
	template: `

This is the home page

ROWDY MERMAID KOMBUCHA

BOULDER, COLORADO

	`

};

angular.module('RowdyMermaid-site.home')
	.component('home', home);

home.$inject = [];
<commit_msg>add image src for image product placed above site name for issue 13<commit_after>const home = {
	bindings: {

	},
	controller: function () {
		let ctrl = this;
		ctrl.title = 'Rowdy Mermaid';

		ctrl.$onInit = function () {
			console.log('init Home');
		};
		ctrl.$postLink = function () {};
	},
	template: `

This is the home page

ROWDY MERMAID KOMBUCHA

BOULDER, COLORADO

	`

};

angular.module('RowdyMermaid-site.home')
	.component('home', home);

home.$inject = [];
<|endoftext|>