<commit_before>'use strict';

const SunCalc = require('suncalc');
const Redis = require('node-redis')

var lat = undefined;
var lon = undefined;
var db_key = undefined;

if(typeof process.env.lat !== 'undefined') {
  lat = process.env.lat;
} else {
  throw "'lat' environmental variable is not set. Set it and restart container";
}

if(typeof process.env.lon !== 'undefined') {
  lon = process.env.lon;
} else {
  throw "'lon' environmental variable is not set. Set it and restart container";
}

if(typeof process.env.db_key !== 'undefined') {
  db_key = process.env.db_key;
} else {
  throw "'db_key'(redis key for publishing sun state) environmental variable is not set. Set it and restart container";
}

console.log(`Tracking the Sun state for coordinates ${lat} ${lon}`);

var preciseState="unknown";
var state="unknown";

var preciseToNormal = {
  "sunrise" : "twilight",
  "sunriseEnd": "day",
  "goldenHourEnd": "day",
  "solarNoon": "day",
  "goldenHour": "day",
  "sunsetStart": "day",
  "sunset": "twilight",
  "dusk": "night",
  "nauticalDusk": "night",
  "night": "night",
  "nadir": "night",
  "nightEnd": "night",
  "nauticalDawn": "night",
  "dawn": "twilight"
};

function Tick() {
  var now = new Date();
//  console.log(`Current time is ${now}`);
  var times = SunCalc.getTimes(now, lat,  lon);
  var currentPrecState = "unknown";
  var currentDiff = Infinity;
  var currentPrecStateTime = undefined;
  for(var propertyName in times) { //finding current precise state
    var diff = now - times[propertyName];
    if(diff > 0 && diff <= currentDiff) {
      currentPrecState = propertyName;
      currentDiff = diff;
      currentPrecStateTime = times[propertyName];
    }
  }
  if(currentPrecState == "unknown") {
    //all the computed events are in the future. discarding until reaching one of the events
    return;
  }
  if(preciseState !== currentPrecState) {
    console.log(`precise state: ${currentPrecState}`);
    preciseState = currentPrecState;
  }
  var currentState = preciseToNormal[currentPrecState];
  if(state !== currentState) {
    console.log(`state: ${currentState}`);
    state = currentState;

    var pub = Redis.createClient(6379, "redis")
    pub.publish(db_key, state);
    pub.quit();
  }
}

Tick();
setInterval(Tick, 30000);
<commit_msg>Enabling key state key storage along with state publishing<commit_after>'use strict';

const SunCalc = require('suncalc');
const Redis = require('node-redis')

var lat = undefined;
var lon = undefined;
var db_key = undefined;

if(typeof process.env.lat !== 'undefined') {
  lat = process.env.lat;
} else {
  throw "'lat' environmental variable is not set. Set it and restart container";
}

if(typeof process.env.lon !== 'undefined') {
  lon = process.env.lon;
} else {
  throw "'lon' environmental variable is not set. Set it and restart container";
}

if(typeof process.env.db_key !== 'undefined') {
  db_key = process.env.db_key;
} else {
  throw "'db_key'(redis key for publishing sun state) environmental variable is not set. Set it and restart container";
}

console.log(`Tracking the Sun state for coordinates ${lat} ${lon}`);

var preciseState="unknown";
var state="unknown";

var preciseToNormal = {
  "sunrise" : "twilight",
  "sunriseEnd": "day",
  "goldenHourEnd": "day",
  "solarNoon": "day",
  "goldenHour": "twilight",
  "sunsetStart": "twilight",
  "sunset": "twilight",
  "dusk": "night",
  "nauticalDusk": "night",
  "night": "night",
  "nadir": "night",
  "nightEnd": "night",
  "nauticalDawn": "night",
  "dawn": "twilight"
};

function Tick() {
  var now = new Date();
//  console.log(`Current time is ${now}`);
  var times = SunCalc.getTimes(now, lat,  lon);
  var currentPrecState = "unknown";
  var currentDiff = Infinity;
  var currentPrecStateTime = undefined;
  for(var propertyName in times) { //finding current precise state
    var diff = now - times[propertyName];
    if(diff > 0 && diff <= currentDiff) {
      currentPrecState = propertyName;
      currentDiff = diff;
      currentPrecStateTime = times[propertyName];
    }
  }
  if(currentPrecState == "unknown") {
    //all the computed events are in the future. discarding until reaching one of the events
    return;
  }
  if(preciseState !== currentPrecState) {
    console.log(`precise state: ${currentPrecState}`);
    preciseState = currentPrecState;
  }
  var currentState = preciseToNormal[currentPrecState];
  if(state !== currentState) {
    console.log(`state: ${currentState}`);
    state = currentState;

    console.log("Saving state into database...");
    var pub = Redis.createClient(6379, "redis");
    pub.set(db_key, state, Redis.print);
    pub.publish(`${db_key}.subscription`, state, Redis.print);
    pub.quit();
    cosnole.log("State saved in the database");
  }
}

Tick();
setInterval(Tick, 30000);
<|endoftext|>


<commit_before>var app = angular.module('nbaRoutes');

app.service('teamService', function($http, $q){

this.addNewGame = function(gameObj){
		var url = 'https://api.parse.com/1/classes/' + gameObj.homeTeam;

		parseInt(gameObj.homeTeamScore);
		parseInt(gameObj.opponentScore);

		if(gameObj.homeTeamScore > gameObj.opponentScore){
			gameObj.won = true;
		}else{
			gameObj.won = false;
		}
		$http.post('url', data)
			return data;
		}

this.getTeamData = function(team){
		var deferred = $q.defer()
		var url = 'https://api.parse.com/1/classes/' + team;
		$http.get('url')
		.then(function(data){
			var results = data.data.results
			var wins = 0;
			var losses = 0;
			results.forEach(input, function(){
				if(data.data.won === true){
					deferred.resolve(wins++);
				}else {
					deferred.reject(losses++);
				}
			})
		})
	}

	return deferred.promise
});
<commit_msg>put the deferred promise return in teh getTeamData function<commit_after>var app = angular.module('nbaRoutes');

app.service('teamService', function($http, $q){

this.addNewGame = function(gameObj){
		var url = 'https://api.parse.com/1/classes/' + gameObj.homeTeam;

		parseInt(gameObj.homeTeamScore);
		parseInt(gameObj.opponentScore);

		if(gameObj.homeTeamScore > gameObj.opponentScore){
			gameObj.won = true;
		}else{
			gameObj.won = false;
		}
		$http.post('url', data)
			return data;
		}

this.getTeamData = function(team){
		var deferred = $q.defer()
		var url = 'https://api.parse.com/1/classes/' + team;
		$http.get('url')
		.then(function(data){
			var results = data.data.results
			var wins = 0;
			var losses = 0;
			results.forEach(input, function(){
				if(data.data.won === true){
					deferred.resolve(wins++);
				}else {
					deferred.reject(losses++);
				}
				results.wins = wins;
				results.losses = losses;
			})
		})
		return deferred.promise
	}
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var util = require('util');
var express = require('express');
var io = require('socket.io');
var Bogey = require('./lib/bogey');

var argv = require('yargs')
    .usage('Usage: $0 [options]')
    .describe({
        c: 'Config file',
        p: 'Port',
        f: 'File(s) to tail'
    })
    .alias({
        c: 'config',
        p: 'port',
        f: 'file',
        h: 'help'
    })
    .help('h')
    .argv;

var config = {};

if (argv.config) {
    try {
        config = require(argv.config);
    } catch (err) {
        console.error('Error loading config file: ', err.message);
        process.exit(1);
    }
}

var port = argv.port || config.port || 8008;

var visualizations = argv.visualization || config.visualizations || [ '/var/www/bogey-pong' ];

// Set up regular expressions.
var regexps = {
    "apache-combined": "/(:<ip>.*) - - \\[(:<timestamp>.*)\\] \"(:<method>.*) (:<uri>.*) (:<protocol>.*)\"" +
        " (:<statusCode>[0-9]*) (:<responseSize>[0-9]*) \"(:<referrer>.*)\" \"(:<userAgent>.*)\"/"
};

if (config.regexp) {
    for (var name in config.regexp) {
        regexps[name] = regexp;
    }
}

// Check log files passed in with -f or --file.
var logs = [];

var files = argv.file;

if (files) {
    if (!util.isArray(files)) {
        files = [ files ];
    };

    files.forEach(function (file) {
        var filename = file.split(':')[0];
        var regexpName = file.split(':')[1];

        if (!regexps[regexpName]) {
            console.log('Regular expression ' + regexpName + ' not found.');
            process.exit(1);
        }

        logs.push({
            file: filename,
            regexp: regexps[regexpName]
        });
    });
}

if (!logs || !logs.length) {
    logs = config.logs;
}

if (!logs || !logs.length) {
    logs = [];
}

// Check log files specified in the config file.
logs.forEach(function (log) {
    var regexp;

    if (regexps[log.regexp]) {
        regexp = regexps[log.regexp];
    } else {
        regexp = log.regexp;
    }

    log.regexp = eval(regexp);
});

var io;
var bogey;

var app = express();
app.use(express.static(__dirname + '/dist'));

app.use(express.bodyParser());
app.post('/log', function (req, res) {
    bogey.emit(req.body);

    res.send(200);
});

var server = app.listen(port);

io = io(server);

bogey = new Bogey(io);

bogey.start(logs);
<commit_msg>Fixed default regex for Apache Combined. Fixed undefined in initialization regex from config<commit_after>#!/usr/bin/env node

var util = require('util');
var express = require('express');
var io = require('socket.io');
var Bogey = require('./lib/bogey');

var argv = require('yargs')
    .usage('Usage: $0 [options]')
    .describe({
        c: 'Config file',
        p: 'Port',
        f: 'File(s) to tail'
    })
    .alias({
        c: 'config',
        p: 'port',
        f: 'file',
        h: 'help'
    })
    .help('h')
    .argv;

var config = {};

if (argv.config) {
    try {
        config = require(argv.config);
    } catch (err) {
        console.error('Error loading config file: ', err.message);
        process.exit(1);
    }
}

var port = argv.port || config.port || 8008;

var visualizations = argv.visualization || config.visualizations || [ '/var/www/bogey-pong' ];

// Set up regular expressions.
var regexps = {
    "apache-combined": "/(:<ip>.*) - - \\[(:<timestamp>.*)\\] \"(:<method>.*) (:<uri>.*) (:<protocol>.*)\"" +
        " (:<statusCode>[0-9]*) (:<responseSize>[0-9]*|-) \"(:<referrer>.*)\" \"(:<userAgent>.*)\"/"
};

if (config.regexp) {
    for (var name in config.regexp) {
        (config.regexp[name]) && (regexps[name] = config.regexp[name]);
    }
}

// Check log files passed in with -f or --file.
var logs = [];

var files = argv.file;

if (files) {
    if (!util.isArray(files)) {
        files = [ files ];
    };

    files.forEach(function (file) {
        var filename = file.split(':')[0];
        var regexpName = file.split(':')[1];

        if (!regexps[regexpName]) {
            console.log('Regular expression ' + regexpName + ' not found.');
            process.exit(1);
        }

        logs.push({
            file: filename,
            regexp: regexps[regexpName]
        });
    });
}

if (!logs || !logs.length) {
    logs = config.logs;
}

if (!logs || !logs.length) {
    logs = [];
}

// Check log files specified in the config file.
logs.forEach(function (log) {
    var regexp;

    if (regexps[log.regexp]) {
        regexp = regexps[log.regexp];
    } else {
        regexp = log.regexp;
    }

    log.regexp = eval(regexp);
});

var io;
var bogey;

var app = express();
app.use(express.static(__dirname + '/dist'));

app.use(express.bodyParser());
app.post('/log', function (req, res) {
    bogey.emit(req.body);

    res.send(200);
});

var server = app.listen(port);

io = io(server);

bogey = new Bogey(io);

bogey.start(logs);
<|endoftext|>


<commit_before>'use strict'

var async = require('async')
var express = require('express')
var fs = require('fs')
var mailin = require('mailin')
var multiparty = require('multiparty')
var util = require('util')

/* Make an http server to receive the webhook. */
var server = express()

/* OPTIONS */
var save_fields = ['subject', 'text', 'from', 'to', 'cc', 'receivedDate']

// You can provide fields during construction or just set the later.
function SimpleMessage (msg) => {
  // @todo: copy msg items over
}

/* This returns a well-formatted string fit for printing to console or saving */
/* Note: The order of the items (subject, sender, etc) is dependent on the
         order in which they were added.                                      */
// @todo: Enforce strict ordering
SimpleMessage.prototype.toString = () => {
  // Items that match save_fields take priority because they're ordered
  for (var field in save_fields) {
    // @todo: define some helper functions for custom formatting
    // for example, 'to' could contain multiple items and I'd want to print them
    // with a format like 'Nick (nick@mail.com), Jessica (jess@mail.com)'
    console.log(field)
  }

  // There should not be any items that aren't in save_fields, but just in case:

  // @todo: I'm sure theres a one-liner for "cookie cutting". I want to create
  // a list of items that aren't in save_fields.
  var non_save_fields = {}
  // populate
  // print
  for (var non_field in non_save_fields) {
    console.log(non_field)
  }
}

/* Grabs the important parts of a message (name, subject, etc) */
// For now, it only prints the results.
function writeMessage (msg) {
  var simpleMessage = new SimpleMessage()

  // @todo: perhaps move this logic to SimpleMessage
  // or perhaps get rid of this altogether?
  for (var item in save_fields) {
    var val = msg[item]
    if (val) {
      simpleMessage[item] = val
    } else {
      simpleMessage[item] = '--'
    }
  }

  console.log(simpleMessage)
}

server.head('/webhook', function (req, res) {
  console.log('Received head request from webhook.')
  res.send(200)
})

server.post('/webhook', function (req, res) {
  console.log('Receiving webhook.')

  /* Respond early to avoid timouting the mailin server. */
  // res.send(200)

  /* Parse the multipart form. The attachments are parsed into fields and can
   * be huge, so set the maxFieldsSize accordingly. */
  var form = new multiparty.Form({
    maxFieldsSize: 70000000
  })

  form.on('progress', (function () {
    var start = Date.now()
    var lastDisplayedPercentage = -1
    return function (bytesReceived, bytesExpected) {
      var elapsed = Date.now() - start
      var percentage = Math.floor(bytesReceived / bytesExpected * 100)
      if (percentage % 20 === 0 && percentage !== lastDisplayedPercentage) {
        lastDisplayedPercentage = percentage
        console.log('Form upload progress ' +
            percentage + '% of ' + bytesExpected / 1000000 + 'Mb. ' + elapsed + 'ms')
      }
    }
  }()))

  form.parse(req, function (err, fields) {
    // Important: `fields` is a string representation of an object wrapped in an array.
    var parsedMailinMsg = JSON.parse(fields.mailinMsg)

    // To avoid headaches later, make sure the message type is Object not Array.
    var message = Array.isArray(parsedMailinMsg) ? parsedMailinMsg[0] : parsedMailinMsg

    if (!err) {
      console.log(util.inspect(message, {
        depth: 5
      }))

      writeMessage(message)

      /* Write down the payload for ulterior inspection. */
      async.auto({
        writeParsedMessage: function (cbAuto) {
          fs.writeFile('payload.json', fields.mailinMsg, cbAuto)
        },
        writeAttachments: function (cbAuto) {
          async.eachLimit(message.attachments, 3, function (attachment, cbEach) {
            fs.writeFile(attachment.generatedFileName, fields[attachment.generatedFileName], 'base64', cbEach)
          }, cbAuto)
        }
      }, function (err) {
        if (err) {
          console.log(err.stack)
          res.send(500, 'Unable to write payload')
        } else {
          console.log('Webhook payload written.')
          res.send(200)
        }
      })
    } else {
      console.log(err)
    }
  })
})

var http = server.listen(8001, function (err) {
  if (err) {
    console.log(err)
  } else {
    console.log('Http server listening on port 3000')
  }
})

process.on('uncaughtException', function (err) {
  console.log(err)
  http.close()
})

process.on('SIGTERM', function (err) {
  console.log(err)
  http.close()
})

mailin.start({
  port: 25,
  webhook: 'http://127.0.0.1:8001/webhook'
})
<commit_msg>Removed arrow functions (was incorrect). Turned SimpleMessage into a class.<commit_after>'use strict'

var async = require('async')
var express = require('express')
var fs = require('fs')
var mailin = require('mailin')
var multiparty = require('multiparty')
var util = require('util')

/* Make an http server to receive the webhook. */
var server = express()

/* OPTIONS */
var save_fields = ['subject', 'text', 'from', 'to', 'cc', 'receivedDate']

// You can provide fields during construction or just set the later.
class SimpleMessage {
  // @todo: copy msg items over
  constructor (msg) {

  }

  /* This returns a well-formatted string fit for printing to console or saving */
  /* Note: The order of the items (subject, sender, etc) is dependent on the
           order in which they were added.                                      */
  // @todo: Enforce strict ordering
  valueOf () {
    // Items that match save_fields take priority because they're ordered
    for (let field in save_fields) {
      // @todo: define some helper functions for custom formatting
      // for example, 'to' could contain multiple items and I'd want to print them
      // with a format like 'Nick (nick@mail.com), Jessica (jess@mail.com)'
      console.log(field)
    }

    // There should not be any items that aren't in save_fields, but just in case:

    // @todo: I'm sure theres a one-liner for "cookie cutting". I want to create
    // a list of items that aren't in save_fields.
    var non_save_fields = {}
    // populate
    // print
    for (let field in non_save_fields) {
      console.log(field)
    }
  }
}

/* Grabs the important parts of a message (name, subject, etc) */
// For now, it only prints the results.
function writeMessage (msg) {
  var simpleMessage = new SimpleMessage()

  // @todo: perhaps move this logic to SimpleMessage
  // or perhaps get rid of this altogether?
  for (var item in save_fields) {
    var val = msg[item]
    if (val) {
      simpleMessage[item] = val
    } else {
      simpleMessage[item] = '--'
    }
  }

  console.log(simpleMessage)
}

server.head('/webhook', function (req, res) {
  console.log('Received head request from webhook.')
  res.send(200)
})

server.post('/webhook', function (req, res) {
  console.log('Receiving webhook.')

  /* Respond early to avoid timouting the mailin server. */
  // res.send(200)

  /* Parse the multipart form. The attachments are parsed into fields and can
   * be huge, so set the maxFieldsSize accordingly. */
  var form = new multiparty.Form({
    maxFieldsSize: 70000000
  })

  form.on('progress', (function () {
    var start = Date.now()
    var lastDisplayedPercentage = -1
    return function (bytesReceived, bytesExpected) {
      var elapsed = Date.now() - start
      var percentage = Math.floor(bytesReceived / bytesExpected * 100)
      if (percentage % 20 === 0 && percentage !== lastDisplayedPercentage) {
        lastDisplayedPercentage = percentage
        console.log('Form upload progress ' +
            percentage + '% of ' + bytesExpected / 1000000 + 'Mb. ' + elapsed + 'ms')
      }
    }
  }()))

  form.parse(req, function (err, fields) {
    // Important: `fields` is a string representation of an object wrapped in an array.
    var parsedMailinMsg = JSON.parse(fields.mailinMsg)

    // To avoid headaches later, make sure the message type is Object not Array.
    var message = Array.isArray(parsedMailinMsg) ? parsedMailinMsg[0] : parsedMailinMsg

    if (!err) {
      console.log(util.inspect(message, {
        depth: 5
      }))

      writeMessage(message)

      /* Write down the payload for ulterior inspection. */
      async.auto({
        writeParsedMessage: function (cbAuto) {
          fs.writeFile('payload.json', fields.mailinMsg, cbAuto)
        },
        writeAttachments: function (cbAuto) {
          async.eachLimit(message.attachments, 3, function (attachment, cbEach) {
            fs.writeFile(attachment.generatedFileName, fields[attachment.generatedFileName], 'base64', cbEach)
          }, cbAuto)
        }
      }, function (err) {
        if (err) {
          console.log(err.stack)
          res.send(500, 'Unable to write payload')
        } else {
          console.log('Webhook payload written.')
          res.send(200)
        }
      })
    } else {
      console.log(err)
    }
  })
})

var http = server.listen(8001, function (err) {
  if (err) {
    console.log(err)
  } else {
    console.log('Http server listening on port 3000')
  }
})

process.on('uncaughtException', function (err) {
  console.log(err)
  http.close()
})

process.on('SIGTERM', function (err) {
  console.log(err)
  http.close()
})

mailin.start({
  port: 25,
  webhook: 'http://127.0.0.1:8001/webhook'
})

var testMessage = new SimpleMessage()
console.log(testMessage + '')
<|endoftext|>


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

(function () {

    var fs = require('fs');

    var generator = null;
    var config = null;
    var logger = null;

    var PLUGIN_ID = require("./package.json").name;
    var SK_MENU_ID = PLUGIN_ID;
    var NATIVE_MENU_ID = PLUGIN_ID + "_native";
    var EXPORT_ALL_ID = PLUGIN_ID + "_full";
    var CROP_ALL_ID = PLUGIN_ID + "_cropped";

    var activeDocumentId = null;
    var lastMenuClicked = null;
    var activeDocumentRoot = null;

    function init(_generator, _config, _logger) {

        generator = _generator;
        config = _config;
        logger = _logger;
        logger.info("REALLY intializing DA-Export with config %j", _config);

        initializeMenus();

        generator.onPhotoshopEvent("currentDocumentChanged", handleActiveDocumentChanged);
        generator.onPhotoshopEvent("generatorMenuChanged", handleMenuClicked);

        //prefill the current document if there is one
        generator.evaluateJSXString("app.activeDocument.id").then(function(id){
            handleActiveDocumentChanged(id);

            //useful for testing a file that's open
            logger.info("RUNNING SPRITEKIT EXPORT");
            lastMenuClicked = "spritekit";
            handleExport();
        });
    }

    function handleActiveDocumentChanged(id)
    {
        activeDocumentId = id;
    };

    function handleMenuClicked(event)
    {
        var menu = event.generatorMenuChanged;
        if (!menu)
        {
            return;
        }

        if (activeDocumentId === null)
        {
            logger.warn("Ignoring menu click without a current document.");
            return;
        }

        // Ignore changes to other menus
        if (menu.name == SK_MENU_ID)
        {
            lastMenuClicked = "spritekit";
        }else if(menu.name == NATIVE_MENU_ID){
            lastMenuClicked = "native_ui";
        }else if(menu.name == EXPORT_ALL_ID){
            lastMenuClicked = "export_all";
        }else if(menu.name == CROP_ALL_ID){
            lastMenuClicked = "crop_all";
        }else{
          return;
        }

        handleExport();
    }

    function handleExport()
    {
        logger.info("STARTING " + lastMenuClicked + " FOR " + activeDocumentId);

        generator.getDocumentInfo(activeDocumentId).then(function(document) {
            //console.log(document);
            var path = document.file;
            var folder = document.file.replace(".psd","");

            activeDocumentRoot = folder;
            prepExportDirectory();
        });
    }

    function prepExportDirectory()
    {
        if (fs.existsSync(activeDocumentRoot))
        {
            try
            {
                var files = fs.readdirSync(activeDocumentRoot);
            }catch(e) {
                console.warn("UNABLE TO GET FILES IN " + activeDocumentRoot);
                return;
            }
            for (var i = 0; i < files.length; i++)
            {
                var filePath = activeDocumentRoot + '/' + files[i];
                if (fs.statSync(filePath).isFile())
                {
                    fs.unlinkSync(filePath);
                }
            }
        }else{
            fs.mkdirSync(activeDocumentRoot);
        }
    }

    function initializeMenus()
    {
        //export all layers/containers for spritekit
        var SK_MENU_LABEL = "DA -> Export SpriteKit";

        //export all layers/containers for native UI
        var NATIVE_MENU_LABEL = "DA -> Export Native UI";

        //export all layers with no metadata and no cropping
        var EXPORT_ALL_LABEL = "DA -> Export Full Sized"

        //export all layers with no metadata and no cropping
        var CROP_ALL_LABEL = "DA -> Export Cropped"

        //name, displayName, enabled, checked
        generator.addMenuItem(SK_MENU_ID, SK_MENU_LABEL, true, false);
        generator.addMenuItem(NATIVE_MENU_ID, NATIVE_MENU_LABEL, true, false);
        generator.addMenuItem(EXPORT_ALL_ID, EXPORT_ALL_LABEL, true, false);
        generator.addMenuItem(CROP_ALL_ID, CROP_ALL_LABEL, true, false);
    }

    exports.init = init;
}());

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

(function () {

    var fs = require('fs');
    var path = require('path');

    var generator = null;
    var config = null;
    var logger = null;

    var PLUGIN_ID = require("./package.json").name;
    var SK_MENU_ID = PLUGIN_ID;
    var NATIVE_MENU_ID = PLUGIN_ID + "_native";
    var EXPORT_ALL_ID = PLUGIN_ID + "_full";
    var CROP_ALL_ID = PLUGIN_ID + "_cropped";

    var lastMenuClicked = null;
    var activeDocumentId = null;
    var activeDocumentName = null;
    var activeDocumentRoot = null;

    var rootWidth;
    var rootHeight;

    function init(_generator, _config, _logger) {

        generator = _generator;
        config = _config;
        logger = _logger;
        logger.info("REALLY intializing DA-Export with config %j", _config);

        initializeMenus();

        generator.onPhotoshopEvent("currentDocumentChanged", handleActiveDocumentChanged);
        generator.onPhotoshopEvent("generatorMenuChanged", handleMenuClicked);

        //prefill the current document if there is one
        generator.evaluateJSXString("app.activeDocument.id").then(function(id){
            handleActiveDocumentChanged(id);

            //useful for testing a file that's open
            logger.info("RUNNING SPRITEKIT EXPORT");
            lastMenuClicked = "spritekit";
            handleExport();
        });
    }

    function handleActiveDocumentChanged(id)
    {
        activeDocumentId = id;
    };

    function handleMenuClicked(event)
    {
        var menu = event.generatorMenuChanged;
        if (!menu)
        {
            return;
        }

        if (activeDocumentId === null)
        {
            logger.warn("Ignoring menu click without a current document.");
            return;
        }

        // Ignore changes to other menus
        if (menu.name == SK_MENU_ID)
        {
            lastMenuClicked = "spritekit";
        }else if(menu.name == NATIVE_MENU_ID){
            lastMenuClicked = "native_ui";
        }else if(menu.name == EXPORT_ALL_ID){
            lastMenuClicked = "export_all";
        }else if(menu.name == CROP_ALL_ID){
            lastMenuClicked = "crop_all";
        }else{
          return;
        }

        handleExport();
    }

    function handleExport()
    {
        logger.info("STARTING " + lastMenuClicked + " FOR " + activeDocumentId);

        generator.getDocumentInfo(activeDocumentId).then(function(document) {
            // console.log(document);
            var document_path = document.file;
            var folder = document_path.replace(".psd","");
            var name = path.basename(document_path);

            activeDocumentName = name.replace(".psd","");
            activeDocumentRoot = folder;

            prepExportDirectory();
            updateMetadata(document);
        });
    }

    function prepExportDirectory()
    {
        if (fs.existsSync(activeDocumentRoot))
        {
            try
            {
                var files = fs.readdirSync(activeDocumentRoot);
            }catch(e) {
                logger.warn("UNABLE TO GET FILES IN " + activeDocumentRoot);
                return;
            }
            for (var i = 0; i < files.length; i++)
            {
                var filePath = activeDocumentRoot + '/' + files[i];
                if (fs.statSync(filePath).isFile())
                {
                    fs.unlinkSync(filePath);
                }
            }
        }else{
            fs.mkdirSync(activeDocumentRoot);
        }
    }

    function updateMetadata(document)
    {
        console.log("UPDATING METADATA");
        if(lastMenuClicked == EXPORT_ALL_ID || lastMenuClicked == CROP_ALL_ID)
        {
            logger.info("no metadata for target " + lastMenuClicked);
            return;
        }

        rootWidth = document.bounds.right - document.bounds.left;
        rootHeight = document.bounds.bottom - document.bounds.top;

        var metadata = {
            root_width: rootWidth,
            root_height: rootHeight,
            coordinate_system: lastMenuClicked,
            children: convertLayersToChildren(document.layers, null)
        }

        console.log("******************************************");
        console.log(JSON.stringify(metadata));
        printSceneGraph(metadata,0);
        console.log("******************************************");

        var metadata_path = activeDocumentRoot + "/" + activeDocumentName + ".txt";
        console.log("WRITING FILE: " + activeDocumentName + ".txt");
        fs.writeFile(metadata_path, JSON.stringify(metadata));
    }

    function printSceneGraph(container, depth)
    {

        var indent = "--";
        for(var i = 0; i < depth; i++)
        {
            indent = indent + "--";
        }

        if(depth == 0)
        {
            console.log("root");
        }

        for(var i = 0; i < container.children.length; i++)
        {
            console.log(indent + container.children[i].name + "(" + container.children[i].type + ")");

            if(container.children[i].type == "container")
            {
                printSceneGraph(container.children[i], depth + 1);
            }
        }
    }

    function convertLayersToChildren(layers, parent)
    {
        var children = [];

        //process back to front so that we can simply work through
        //the list and add children as we go on the front-end side
        for(var i = layers.length-1; i >= 0; i--)
        {
            var meta_node = null;

            if(layers[i].layers)
            {
                console.log("PROCESS CHILDREN RECURSIVE.......");
                meta_node = processGroup(layers[i]);

                //if it was an organizational group, just add the nodes!
                if(meta_node != null && meta_node.type == "flatten")
                {
                    for(var j = 0; j < meta_node.children.length; j++)
                    {
                        children.push(meta_node.children[j]);

                        //fill in the position relative to the parent
                        if(parent == null)
                        {
                            meta_node.children[j].position = meta_node.children[j].position_absolute
                        }else{
                            meta_node.children[j].position = [meta_node.children[j].position_absolute[0] - parent.position_absolute[0], meta_node.children[j].position_absolute[1] - parent.position_absolute[1]]
                        }

                    }

                    meta_node = null;
                }

            }else{
                console.log("PROCESS SINGLE LAYER.......");
                meta_node = processLayer(layers[i]);
            }

            //don't assume we got a node! might be a guide container or our options layer
            if(meta_node == null)
            {
                continue;
            }

            //fill in the relative position
            if(parent == null)
            {
                meta_node.position = meta_node.position_absolute
            }else{
                console.log("CALCULATING POSITION FOR " + meta_node.name);
                meta_node.position = [meta_node.position_absolute[0] - parent.position_absolute[0],meta_node.position_absolute[1] - parent.position_absolute[1]];
                console.log("POSITION: " + meta_node.position);
                console.log("ABSOLUTE: " + meta_node.position_absolute);
                console.log("PARENT ABSOLUTE: " + parent.position_absolute);
            }

            if(meta_node.pivot_absolute != null)
            {
                var pivot_relative;
                if(parent == null)
                {
                    pivot_relative = meta_node.pivot_absolute;
                }else{
                    pivot_relative = [meta_node.pivot_absolute[0] - parent.position_absolute[0],meta_node.pivot_absolute[1] - parent.position_absolute[1]]
                }

                //our pivot should ACTUALLY be our position, but record the delta in the pivot variable
                var old_position = meta_node.position; //swap
                meta_node.position = pivot_relative;
                meta_node.pivot = [old_position[0] - pivot_relative[0], old_position[1] - pivot_relative[1]];
            }

            children.push(meta_node);
        }

        return children;
    }

    //GUIDE, CONTAINER, (nothing), PROGRESS, SCALE9, BTN, SCALEBTN, TAB
    function processGroup(group)
    {
        console.log(".... process group '" + group.name + "'' with " + group.layers.length + " children");

        //ignore anything in a guide folder
        if(group.name.indexOf("guide") == 0) return null;

        var group_name = group.name.replace(/ /g, "_");
        console.log(group_name);

        var CONTAINER_ALIASES = ["container", "progress", "scale9", "btn", "scalebtn", "tab", "paragraph"];
        var group_type = group_name.split("_")[0];

        var meta_node = {
            "name":group_name,
            "type":null
        };
        console.log("meta_node created");

        var center_rect = extractCenterAndSize(group.bounds);
        console.log(center_rect);
        meta_node["position_absolute"] = [center_rect[0], center_rect[1]];
        meta_node["size"] = [center_rect[2], center_rect[3]];

        console.log(meta_node);

        if(CONTAINER_ALIASES.indexOf(group_type) >= 0)
        {
            meta_node.type = "container";
        }else{
            meta_node.type = "flatten";
        }

        console.log(meta_node);
        console.log("fetching children...");

        meta_node["children"] = convertLayersToChildren(group.layers, meta_node);

        //see if we have a pivot node!
        for(var i = 0; i < meta_node["children"].length; i++)
        {
            if(meta_node["children"][i]["type"] == "pivot")
            {
                //copy that node's position as my pivot
                meta_node["pivot_absolute"] = meta_node["children"][i]["position_absolute"]

                //delete the pivot node
                meta_node["children"].splice(i,1);

                //only one pivot allowed, so we can bail here
                break;
            }
        }

        console.log("RETURNING META NODE");
        console.log(meta_node);
        return meta_node;
    }

    function extractCenterAndSize(bounds)
    {
        console.log("EXTRACTING CENTER AND SIZE");

        if(lastMenuClicked == "spritekit")
        {
            var width = bounds.right - bounds.left;
            var height = bounds.bottom - bounds.top; //y-down

            var center_x = bounds.left + width/2;
            var center_y = bounds.bottom - height/2;

            console.log("CENTER: " + center_x + "," + center_y);

            center_x = center_x - rootWidth/2;  //convert to origin at center
            center_y = rootHeight/2 - center_y;  //convert to origin at center, y-positive

            console.log("RETURNING " + [center_x,center_y,width,height]);
            return [center_x, center_y, width, height];
        }else if(lastMenuClicked == "native_ui"){

            var width = bounds.right - bounds.left;
            var height = bounds.bottom - bounds.top; //y-down

            console.log("RETURNING " + [bounds.left, bounds.top, width, height]);
            return [bounds.left, bounds.top, width, height];
        }
    }

    //GUIDE, OPTIONS, TEXT, PIVOT, PLACEHOLDER, TILE, IMAGE, SCALEBTN
    function processLayer(layer) {
        var layerName = layer.name;

        if(layer.name.indexOf("guide") == 0) return null;
        if(layer.name == "options") return null;

        var center_rect = extractCenterAndSize(layer.bounds);

        if(layer.boundsWithFX != null)
        {
            console.log("HAS FX BOUNDS: " + layerName);
            console.log("BOUNDS: " + layer.bounds.left + "," + layer.bounds.top + "," + (layer.bounds.right - layer.bounds.left) + "," + (layer.bounds.bottom - layer.bounds.top));
            console.log("FX BOUNDS: " + layer.boundsWithFX.left + "," + layer.boundsWithFX.top + "," + (layer.boundsWithFX.right - layer.boundsWithFX.left) + "," + (layer.boundsWithFX.bottom - layer.boundsWithFX.top));
            console.log("BEFORE: " + center_rect);
            center_rect = extractCenterAndSize(layer.boundsWithFX);
            console.log("AFTER: " + center_rect);
        }

        var position = [center_rect[0], center_rect[1]];
        var size = [center_rect[2], center_rect[3]];

        if(layer.name.indexOf("text") == 0)
        {
            if(layer.text != null)
            {
                // console.log("******************* TEXT " + layer.name);
                // console.log("POSITION: " + position);
                // console.log("SIZE: " + size);
                // console.log(layer.text);
                // console.log(layer.text.textStyleRange[0]);
                // console.log(layer.text.paragraphStyleRange[0]);

                //splitting these out just in case I need to debug
                var default_text = layer.text.textKey;

                //DEFAULT VALUES
                var text_color = "000000";
                var text_font = "Arial";
                var text_fontStyle = "Black";
                var text_justification = "left"
                var text_size = 24;
                var alpha = 1.0;

                if(layer.blendOptions != null)
                {
                    console.log("BLEND OPTIONS FOUND");
                    console.log(layer.blendOptions);
                    if(layer.blendOptions.hasOwnProperty("opacity"))
                    {
                        alpha = layer.blendOptions.opacity.value / 100.0;
                        console.log("ALPHA SET TO " + alpha);
                    }
                }

                try
                {
                    var text_style = layer.text.textStyleRange[0].textStyle;

                    logger.error("++++++++++++++++++++++++++");
                    logger.error(text_style);

                    text_font = text_style.fontName;
                    text_fontStyle = text_style.fontStyleName;

                    if(text_style.hasOwnProperty("size"))
                    {
                        if(text_style.size.hasOwnProperty("value"))
                        {
                            console.log("TEXT STYLE SIZE HAS VALUE");
                            text_size = text_style.size.value;
                        }else{
                            console.log("TEXT STYLE SIZE HAS NO VALUE");
                            console.log(text_style);
                            console.log(text_style.size);
                            text_size = text_style.size;
                        }
                    }else{
                        console.log("TEXT STYLE HAS NO SIZE");
                        console.log(text_style);
                    }

                    var red_value = 0;
                    var green_value = 0;
                    var blue_value = 0;

                    if(text_style.color.red != undefined) red_value = text_style.color.red;
                    if(text_style.color.green != undefined) green_value = text_style.color.green;
                    if(text_style.color.blue != undefined) blue_value = text_style.color.blue;

                    var red = Math.round(red_value).toString(16);
                    var green = Math.round(green_value).toString(16);
                    var blue = Math.round(blue_value).toString(16);

                    if(red.length < 2) red = "0" + red;
                    if(green.length < 2) green = "0" + green;
                    if(blue.length < 2) blue = "0" + blue;

                    text_color = red + green + blue;

                }catch(e){
                    logger.error("ERROR PARSING FONT STYLE -- " + e);
                    logger.error(layer.text.textKey);
                    logger.error(layer.text.textStyleRange);
                }

                if(layer.text.paragraphStyleRange == null)
                {
                    console.log("PARAGRAPH STYLE = null");
                }else{
                    try
                    {
                        var par_style = layer.text.paragraphStyleRange[0].paragraphStyle;
                        text_justification = par_style.align;

                        switch(text_justification)
                        {
                            case "left":
                                //adjust position for left align
                                if(lastMenuClicked == "spritekit")
                                {
                                    position[0] = position[0] - size[0]/2;
                                }else if(lastMenuClicked == "native_ui"){
                                    //already left aligned by default!
                                }else{
                                    logger.warn("ERROR: DONT KNOW HOW TO PROCESS lastMenuClicked " + lastMenuClicked);
                                }

                                break;
                            case "right":
                                if(lastMenuClicked == "spritekit")
                                {
                                    position[0] = position[0] + size[0]/2;
                                }else if(lastMenuClicked == "native_ui"){
                                    position[0] = position[0] + size[0];
                                }else{
                                    logger.warn("ERROR: DONT KNOW HOW TO PROCESS lastMenuClicked " + lastMenuClicked);
                                }

                                break;
                            case "center":
                                if(lastMenuClicked == "spritekit")
                                {
                                    //center aligned by default!
                                }else if(lastMenuClicked == "native_ui"){
                                    position[0] = position[0] + size[0]/2;
                                }else{
                                    logger.warn("ERROR: DONT KNOW HOW TO PROCESS lastMenuClicked " + lastMenuClicked);
                                }
                                break;
                        }

                    }catch(e){
                        logger.error("ERROR PARSING PARAGRAPH STYLE -- " + e);
                        logger.error(layer.text.paragraphStyleRange[0]);
                    }
                }

                if(layer.text.transform)
                {
                    text_size = text_size * layer.text.transform.xx;
                }

                return {
                    "name" : layer.name.substr(5).replace(/ /g,"_"),
                    "type" : "text",
                    "position_absolute" : position,
                    "size": size,
                    "color":text_color,
                    "font":text_font + "-" + text_fontStyle,
                    "justification":text_justification,
                    "fontSize":text_size,
                    "text": default_text,
                    "alpha":alpha
                };
            }
        }

        if(layer.name.indexOf("pivot") == 0)
        {
            return { "name" : layer.name.substr(6).replace(/ /g,"_"), "type" : "pivot", "position_absolute" : position };
        }

        if(layer.name.indexOf("placeholder") == 0)
        {
            return { "name" : layer.name.substr(12).replace(/ /g,"_"), "type" : "placeholder", "position_absolute":position, "size":size }
        }

        //tile_NAMEOFTEXTURE is a special kind of placeholder where we look for an image named NAMEOFTEXTURE and tile it horizontally & vertically to fill the rect
        if(layer.name.indexOf("tile") == 0)
        {
            return { "name" : layer.name.replace(/ /g,"_"), "type" : "placeholder", "position_absolute":position, "size":size }
        }

        if(layer.name.indexOf("alias") == 0)
        {
            //trim off the "alias_" and the metadata points to a pre-existing image!
            return { "name" : layer.name.substr(6).replace(/ /g,"_"), "type" : "image", "position_absolute" : position };
        }

        //IMAGE OR SCALEBTN
        return { "name" : layer.name.replace(/ /g,"_"), "type" : "image", "position_absolute" : position, "size":size };
    }

    function initializeMenus()
    {
        //export all layers/containers for spritekit
        var SK_MENU_LABEL = "DA -> Export SpriteKit";

        //export all layers/containers for native UI
        var NATIVE_MENU_LABEL = "DA -> Export Native UI";

        //export all layers with no metadata and no cropping
        var EXPORT_ALL_LABEL = "DA -> Export Full Sized"

        //export all layers with no metadata and no cropping
        var CROP_ALL_LABEL = "DA -> Export Cropped"

        //name, displayName, enabled, checked
        generator.addMenuItem(SK_MENU_ID, SK_MENU_LABEL, true, false);
        generator.addMenuItem(NATIVE_MENU_ID, NATIVE_MENU_LABEL, true, false);
        generator.addMenuItem(EXPORT_ALL_ID, EXPORT_ALL_LABEL, true, false);
        generator.addMenuItem(CROP_ALL_ID, CROP_ALL_LABEL, true, false);
    }

    exports.init = init;
}());

   <|endoftext|>


<commit_before>/*jslint browser: true, nomen: true, todo: true */
(function ($, window, document) {
    'use strict';

    var P, PicPlus, loadImage,
        LoadQueue, loadQueue,
        $win = $(window),
        MQL_DATA = 'picplus-mql',
        ACTIVE_CLASS = 'picplus-active',
        Autoload = {
            IMMEDIATE: 'immediate',
            LAZY: 'lazy',
            NONE: 'none'
        };

    // The default image loader. The plugin has a list of image loaders and
    // passes each one the image attributes. The loader can choose whether it
    // wants to load the image or not. If it decides it should load the image,
    // it should return a promise which is resolved with a DOM element
    // representing the loaded image. Otherwise, it should return `null`.
    loadImage = function (attrs) {
        var deferred = $.Deferred(),
            img = new Image();
        img.onload = function () {
            deferred.resolve(img);
        };
        $.extend(img, attrs);
        return deferred.promise();
    };

    LoadQueue = function () {};
    LoadQueue.create = function () {
        var lq = new LoadQueue();
        lq.items = [];
        lq.loaders = [loadImage];
        return lq;
    };
    LoadQueue.prototype = {
        simultaneous: 3,
        _loadingCount: 0,
        _loadNext: function () {
            var nextItem;
            if (!this.items.length || this._loadingCount >= this.simultaneous) {
                return;
            }
            nextItem = this.items.pop();  // Load most recently queued first.
            this._loadingCount += 1;
            nextItem();
        },
        _onComplete: function () {
            this._loadingCount -= 1;
            this._loadNext();
        },
        load: function (imgAttrs) {
            var loadNow,
                self = this,
                deferred = $.Deferred(),
                promise = deferred.promise(),
                onComplete = $.proxy(this._onComplete, this);

            // TODO: Support timeout
            loadNow = function () {
                var p;

                // Loop through the loaders until we find one to use.
                $.each(self.loaders, function (i, loader) {
                    p = loader(imgAttrs);
                    if (p) {
                        p
                            .then(deferred.resolve)
                            .then(onComplete);
                        return false;
                    }
                });

                if (!p) {
                    $.error('No loader found for image.', imgAttrs)
                }
            };

            this.items.push(loadNow);
            this._loadNext();
            return promise;
        }
    };

    P = PicPlus = function () {};
    PicPlus.prototype = {

        defaultOptions: {
            // When should the image be loaded?
            autoload: Autoload.IMMEDIATE,

            // Does the image change size when the browser window does?
            responsive: false
        },

        initialize: function ($el, options) {
            this.$el = $el;
            this.options = $.extend({}, this.defaultOptions, this.getHtmlOptions(), options);

            if (this.$el.is('[data-src]')) {
                // Shorthand version
                this.$sources = this.$el;
            } else {
                this.$sources = this.$el.find('[data-src]');
            }

            this._setupMediaQueries();

            if (this.options.responsive && this.$sources.length > 1) {
                // Unfortunately, we can't use MediaQueryList.addListener
                // because if multiple sources match, we need to recalculate
                // to find the *first* match.
                this._onResize = $.proxy(this.load, this);
                $win.on('resize', this._onResize);
            }
            if (this.options.autoload === Autoload.IMMEDIATE) {
                this.load();
            }
            return this;
        },

        // Create a media query list for each source.
        _setupMediaQueries: function () {
            var $el, mql, media;
            if (!window.matchMedia) {
                return;
            }

            this.$sources.each(function (i, el) {
                $el = $(el);
                media = $el.attr('data-media');
                if (media) {
                    mql = window.matchMedia(media);
                    $el.data(MQL_DATA, mql);
                }
            });
        },

        getHtmlOptions: function () {
            var attrs = this.$el[0].attributes;
            return {
                autoload: attrs['data-autoload'],
                alt: attrs.alt
            };
        },

        // Load the appropriate source.
        load: function () {
            var $source, i, media, mql;
            // Iterate in reverse order so that later sources have higher
            // priority.
            for (i = this.$sources.length - 1; i >= 0; i -= 1) {
                $source = this.$sources.eq(i);
                media = $source.attr('data-media');
                mql = $source.data(MQL_DATA);
                if (!media || (window.matchMedia && mql.matches)) {
                    // We should load this source.
                    this._loadSource($source);
                    break;
                }
            }

            return this;
        },

        onImageLoad: function (img, $source) {
            // Make sure this is the image we were waiting for.
            if (!(this._loadingSource && this._loadingSource[0] === $source[0])) {
                return;
            }
            this._loadingSource = null;
            img.onload = null;
            this.showImage($source, img);
        },

        // Show the provided image, for the provided source element.
        showImage: function ($source, img) {
            if (this.$sources.length > 1) {
                this.$sources
                    .hide()
                    .removeClass(ACTIVE_CLASS);
            }
            if (img && !img.parentNode) {
                $source[0].appendChild(img);
            }
            $source
                .addClass(ACTIVE_CLASS)
                .show();
        },

        // Load the source represented by the provided element.
        _loadSource: function ($source) {
            var src, alt, lq,
                self = this,
                promise = $source.data('promise');

            if (promise) {
                if (promise.state() == 'resolved') {
                    this.showImage($source)
                }

                // TODO: Should promote it to top of queue.
                return;
            }

            if (!loadQueue) {
                loadQueue = LoadQueue.create();
            }

            src = $source.attr('data-src');
            alt = $source.attr('data-alt');
            if (alt === null || alt === undefined) {
                alt = this.$el.attr('data-alt');
            }

            promise = loadQueue.load({src: src, alt: alt});
            $source.data('promise', promise);
            this._loadingSource = $source;
            promise.done(function (img) {
                self.onImageLoad(img, $source);
            });
        },

        destroy: function () {
            if (this._onResize) {
                $win.off('resize', this._onResize);
            }
        }
    };

    $.extend($.fn, {
        picplus: function (optionsOrMethod) {
            var returnValue = this,
                args = arguments;
            this.each(function (i, el) {
                var $el = $(el),
                    plugin = $el.data('picplus'),
                    method = typeof optionsOrMethod === 'string' ? optionsOrMethod : null,
                    options = method === null ? optionsOrMethod || {} : {};

                if (!plugin) {
                    if (method) {
                        $.error('You can\'t call the picplus method "' + method
                                + '" without first initializing the plugin by calling '
                                + 'picplus() on the jQuery object.');
                    } else {
                        plugin = new PicPlus().initialize($el, options);
                        $el.data('picplus', plugin);
                    }
                } else if (method) {
                    if (typeof plugin[method] !== 'function') {
                        $.error('Method "' + method + '" does not exist on jQuery.picplus');
                    } else {
                        // NOTE: If you call a method that returns a value, you will only get the result from the last item in the collection.
                        returnValue = plugin[method].apply(plugin, Array.prototype.slice.call(args, 1));
                    }
                }
            });
            return returnValue;
        }
    });

    $.picplus = function (opts) {
        $('[data-picplus]').picplus(opts);
    };

    $.picplus.config = function (opts) {
        // global config opts. for example, how many images to load at once.
    };

}(this.jQuery, window, document));
<commit_msg>Only bind _onComplete once<commit_after>/*jslint browser: true, nomen: true, todo: true */
(function ($, window, document) {
    'use strict';

    var P, PicPlus, loadImage,
        LoadQueue, loadQueue,
        $win = $(window),
        MQL_DATA = 'picplus-mql',
        ACTIVE_CLASS = 'picplus-active',
        Autoload = {
            IMMEDIATE: 'immediate',
            LAZY: 'lazy',
            NONE: 'none'
        };

    // The default image loader. The plugin has a list of image loaders and
    // passes each one the image attributes. The loader can choose whether it
    // wants to load the image or not. If it decides it should load the image,
    // it should return a promise which is resolved with a DOM element
    // representing the loaded image. Otherwise, it should return `null`.
    loadImage = function (attrs) {
        var deferred = $.Deferred(),
            img = new Image();
        img.onload = function () {
            deferred.resolve(img);
        };
        $.extend(img, attrs);
        return deferred.promise();
    };

    LoadQueue = function () {};
    LoadQueue.create = function () {
        var lq = new LoadQueue();
        lq.items = [];
        lq.loaders = [loadImage];
        lq._onComplete = $.proxy(LoadQueue.prototype._onComplete, lq);
        return lq;
    };
    LoadQueue.prototype = {
        simultaneous: 3,
        _loadingCount: 0,
        _loadNext: function () {
            var nextItem;
            if (!this.items.length || this._loadingCount >= this.simultaneous) {
                return;
            }
            nextItem = this.items.pop();  // Load most recently queued first.
            this._loadingCount += 1;
            nextItem();
        },
        _onComplete: function () {
            this._loadingCount -= 1;
            this._loadNext();
        },
        load: function (imgAttrs) {
            var loadNow,
                self = this,
                deferred = $.Deferred(),
                promise = deferred.promise();

            // TODO: Support timeout
            loadNow = function () {
                var p;

                // Loop through the loaders until we find one to use.
                $.each(self.loaders, function (i, loader) {
                    p = loader(imgAttrs);
                    if (p) {
                        p
                            .then(deferred.resolve)
                            .then(self._onComplete);
                        return false;
                    }
                });

                if (!p) {
                    $.error('No loader found for image.', imgAttrs)
                }
            };

            this.items.push(loadNow);
            this._loadNext();
            return promise;
        }
    };

    P = PicPlus = function () {};
    PicPlus.prototype = {

        defaultOptions: {
            // When should the image be loaded?
            autoload: Autoload.IMMEDIATE,

            // Does the image change size when the browser window does?
            responsive: false
        },

        initialize: function ($el, options) {
            this.$el = $el;
            this.options = $.extend({}, this.defaultOptions, this.getHtmlOptions(), options);

            if (this.$el.is('[data-src]')) {
                // Shorthand version
                this.$sources = this.$el;
            } else {
                this.$sources = this.$el.find('[data-src]');
            }

            this._setupMediaQueries();

            if (this.options.responsive && this.$sources.length > 1) {
                // Unfortunately, we can't use MediaQueryList.addListener
                // because if multiple sources match, we need to recalculate
                // to find the *first* match.
                this._onResize = $.proxy(this.load, this);
                $win.on('resize', this._onResize);
            }
            if (this.options.autoload === Autoload.IMMEDIATE) {
                this.load();
            }
            return this;
        },

        // Create a media query list for each source.
        _setupMediaQueries: function () {
            var $el, mql, media;
            if (!window.matchMedia) {
                return;
            }

            this.$sources.each(function (i, el) {
                $el = $(el);
                media = $el.attr('data-media');
                if (media) {
                    mql = window.matchMedia(media);
                    $el.data(MQL_DATA, mql);
                }
            });
        },

        getHtmlOptions: function () {
            var attrs = this.$el[0].attributes;
            return {
                autoload: attrs['data-autoload'],
                alt: attrs.alt
            };
        },

        // Load the appropriate source.
        load: function () {
            var $source, i, media, mql;
            // Iterate in reverse order so that later sources have higher
            // priority.
            for (i = this.$sources.length - 1; i >= 0; i -= 1) {
                $source = this.$sources.eq(i);
                media = $source.attr('data-media');
                mql = $source.data(MQL_DATA);
                if (!media || (window.matchMedia && mql.matches)) {
                    // We should load this source.
                    this._loadSource($source);
                    break;
                }
            }

            return this;
        },

        onImageLoad: function (img, $source) {
            // Make sure this is the image we were waiting for.
            if (!(this._loadingSource && this._loadingSource[0] === $source[0])) {
                return;
            }
            this._loadingSource = null;
            img.onload = null;
            this.showImage($source, img);
        },

        // Show the provided image, for the provided source element.
        showImage: function ($source, img) {
            if (this.$sources.length > 1) {
                this.$sources
                    .hide()
                    .removeClass(ACTIVE_CLASS);
            }
            if (img && !img.parentNode) {
                $source[0].appendChild(img);
            }
            $source
                .addClass(ACTIVE_CLASS)
                .show();
        },

        // Load the source represented by the provided element.
        _loadSource: function ($source) {
            var src, alt, lq,
                self = this,
                promise = $source.data('promise');

            if (promise) {
                if (promise.state() == 'resolved') {
                    this.showImage($source)
                }

                // TODO: Should promote it to top of queue.
                return;
            }

            if (!loadQueue) {
                loadQueue = LoadQueue.create();
            }

            src = $source.attr('data-src');
            alt = $source.attr('data-alt');
            if (alt === null || alt === undefined) {
                alt = this.$el.attr('data-alt');
            }

            promise = loadQueue.load({src: src, alt: alt});
            $source.data('promise', promise);
            this._loadingSource = $source;
            promise.done(function (img) {
                self.onImageLoad(img, $source);
            });
        },

        destroy: function () {
            if (this._onResize) {
                $win.off('resize', this._onResize);
            }
        }
    };

    $.extend($.fn, {
        picplus: function (optionsOrMethod) {
            var returnValue = this,
                args = arguments;
            this.each(function (i, el) {
                var $el = $(el),
                    plugin = $el.data('picplus'),
                    method = typeof optionsOrMethod === 'string' ? optionsOrMethod : null,
                    options = method === null ? optionsOrMethod || {} : {};

                if (!plugin) {
                    if (method) {
                        $.error('You can\'t call the picplus method "' + method
                                + '" without first initializing the plugin by calling '
                                + 'picplus() on the jQuery object.');
                    } else {
                        plugin = new PicPlus().initialize($el, options);
                        $el.data('picplus', plugin);
                    }
                } else if (method) {
                    if (typeof plugin[method] !== 'function') {
                        $.error('Method "' + method + '" does not exist on jQuery.picplus');
                    } else {
                        // NOTE: If you call a method that returns a value, you will only get the result from the last item in the collection.
                        returnValue = plugin[method].apply(plugin, Array.prototype.slice.call(args, 1));
                    }
                }
            });
            return returnValue;
        }
    });

    $.picplus = function (opts) {
        $('[data-picplus]').picplus(opts);
    };

    $.picplus.config = function (opts) {
        // global config opts. for example, how many images to load at once.
    };

}(this.jQuery, window, document));
<|endoftext|>


<commit_before>/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

"use strict";

var config = require('./config.js');

var anyDB = require('any-db');
var mysqlConnection = anyDB.createPool(config.MYSQL_DATABASE_URL, {min: 2, max: 20});

var utils = require('./utils.js');

var async = require('async');

var express = require('express');

var app = express();
app.enable('trust proxy');
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.compress());

app.use('/bootstrap', express.static(__dirname + '/bootstrap'));
app.use('/img', express.static(__dirname + '/img'));
app.use('/browserified', express.static(__dirname + '/browserified'));

var swig = require('swig');
app.engine('html', swig.renderFile);
app.engine('twig', swig.renderFile);
app.engine('swig', swig.renderFile);
app.set('view engine', 'twig'); // historical reasons
app.set('views', __dirname + '/templates');

var phpgate = require('./cgi.js').phpgate;

app.use(function(request, response, next){
	request.uonline = {};
	request.uonline.basicOpts = {};
	async.parallel([
			function(callback){
				utils.user.updateSession(mysqlConnection, request.cookies.sessid, config.sessionExpireTime, callback);
			},
		],
		function(error, result)
		{
			if(!!error)
			{
				response.send(500);
			}
			else
			{
				request.uonline.basicOpts.now = new Date();
				request.uonline.basicOpts.loggedIn = result[0].sessionIsActive;
				request.uonline.basicOpts.login = result[0].username;
				request.uonline.basicOpts.admin = (result[0].permissions == 65535);
				next();
			}
		}
	);
});

/*** routing routines ***/

app.get('/node/', function(request, response) {
	response.send('Node.js is up and running.');
});

/*** real ones ***/

app.get('/', function(request, response) {
	response.redirect((request.uonline.basicOpts.loggedIn === true) ?
		config.defaultInstanceForUsers : config.defaultInstanceForGuests);
});

app.get('/about/', function(request, response) {
	var options = request.uonline.basicOpts;
	options.instance = 'about';
	response.render('about', options);
});

app.get('/register/', function(request, response) {
	var options = request.uonline.basicOpts;
	options.instance = 'register';
	response.render('register', options);
});

app.post('/register/', phpgate);
app.get('/login/', phpgate);
app.post('/login/', phpgate);
app.get('/profile/', phpgate);
app.get('/profile/id/:id/', phpgate);
app.get('/profile/user/:user/', phpgate);
app.get('/action/logout', phpgate);
app.get('/game/', phpgate);
app.get('/action/go/:to', phpgate);
app.get('/action/attack', phpgate);
app.get('/action/escape', phpgate);

app.get('/ajax/isNickBusy/:nick', function(request, response) {
	utils.user.userExists(mysqlConnection, request.param('nick'), function(error, result){
		if (!!error) { response.send(500); return; }
		response.json({ 'nick': request.param('nick'), 'isNickBusy': result });
	});
});

app.get('/stats/', phpgate);
app.get('/world/', phpgate);
app.get('/development/', phpgate);

/*
app.get('/', function(request, response) {
	response.redirect('/about/');
});

app.get('/about/', function(request, response) {
	var t = Date.now();
	var options = {};
	options.instance = 'about';
	options.loggedIn = false;
	response.render('about.twig', options);
	console.log(request.path+' - done in '+(Date.now()-t)+' ms');
});
*/

/***** main *****/
var port = process.env.PORT || 5000;
app.listen(port, function() {
	console.log("Listening on " + port);
	if (port==5000) console.log("Try http://localhost:" + port + "/");
});
<commit_msg>Serve `/login/` natively.<commit_after>/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

"use strict";

var config = require('./config.js');

var anyDB = require('any-db');
var mysqlConnection = anyDB.createPool(config.MYSQL_DATABASE_URL, {min: 2, max: 20});

var utils = require('./utils.js');

var async = require('async');

var express = require('express');

var app = express();
app.enable('trust proxy');
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.compress());

app.use('/bootstrap', express.static(__dirname + '/bootstrap'));
app.use('/img', express.static(__dirname + '/img'));
app.use('/browserified', express.static(__dirname + '/browserified'));

var swig = require('swig');
app.engine('html', swig.renderFile);
app.engine('twig', swig.renderFile);
app.engine('swig', swig.renderFile);
app.set('view engine', 'twig'); // historical reasons
app.set('views', __dirname + '/templates');

var phpgate = require('./cgi.js').phpgate;

app.use(function(request, response, next){
	request.uonline = {};
	request.uonline.basicOpts = {};
	async.parallel([
			function(callback){
				utils.user.updateSession(mysqlConnection, request.cookies.sessid, config.sessionExpireTime, callback);
			},
		],
		function(error, result)
		{
			if(!!error)
			{
				response.send(500);
			}
			else
			{
				request.uonline.basicOpts.now = new Date();
				request.uonline.basicOpts.loggedIn = result[0].sessionIsActive;
				request.uonline.basicOpts.login = result[0].username;
				request.uonline.basicOpts.admin = (result[0].permissions == 65535);
				next();
			}
		}
	);
});

/*** routing routines ***/

app.get('/node/', function(request, response) {
	response.send('Node.js is up and running.');
});

/*** real ones ***/

app.get('/', function(request, response) {
	response.redirect((request.uonline.basicOpts.loggedIn === true) ?
		config.defaultInstanceForUsers : config.defaultInstanceForGuests);
});

app.get('/about/', function(request, response) {
	var options = request.uonline.basicOpts;
	options.instance = 'about';
	response.render('about', options);
});

app.get('/register/', function(request, response) {
	var options = request.uonline.basicOpts;
	options.instance = 'register';
	response.render('register', options);
});

app.post('/register/', phpgate);

app.get('/login/', function(request, response) {
	var options = request.uonline.basicOpts;
	options.instance = 'login';
	response.render('login', options);
});

app.post('/login/', phpgate);
app.get('/profile/', phpgate);
app.get('/profile/id/:id/', phpgate);
app.get('/profile/user/:user/', phpgate);
app.get('/action/logout', phpgate);
app.get('/game/', phpgate);
app.get('/action/go/:to', phpgate);
app.get('/action/attack', phpgate);
app.get('/action/escape', phpgate);

app.get('/ajax/isNickBusy/:nick', function(request, response) {
	utils.user.userExists(mysqlConnection, request.param('nick'), function(error, result){
		if (!!error) { response.send(500); return; }
		response.json({ 'nick': request.param('nick'), 'isNickBusy': result });
	});
});

app.get('/stats/', phpgate);
app.get('/world/', phpgate);
app.get('/development/', phpgate);

/*
app.get('/', function(request, response) {
	response.redirect('/about/');
});

app.get('/about/', function(request, response) {
	var t = Date.now();
	var options = {};
	options.instance = 'about';
	options.loggedIn = false;
	response.render('about.twig', options);
	console.log(request.path+' - done in '+(Date.now()-t)+' ms');
});
*/

/***** main *****/
var port = process.env.PORT || 5000;
app.listen(port, function() {
	console.log("Listening on " + port);
	if (port==5000) console.log("Try http://localhost:" + port + "/");
});
<|endoftext|>


<commit_before>var util = (function () {

var exports = {};

// From MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
exports.random_int = function random_int(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

var favicon_selector = 'link[rel="shortcut icon"]';

// We need to reset the favicon after changing the
// window.location.hash or Firefox will drop the favicon.  See
// https://bugzilla.mozilla.org/show_bug.cgi?id=519028
exports.reset_favicon = function () {
    $(favicon_selector).detach().appendTo('head');
};

exports.set_favicon = function (url) {
    if ($.browser.webkit) {
        // Works in Chrome 22 at least.
        // Doesn't work in Firefox 10.
        $(favicon_selector).attr('href', url);
    } else {
        // Delete and re-create the node.
        // May cause excessive work by the browser
        // in re-rendering the page (see #882).
        $(favicon_selector).remove();
        $('head').append($('<link>')
            .attr('rel',  'shortcut icon')
            .attr('href', url));
    }
};

exports.make_loading_indicator = function (outer_container, opts) {
    opts = opts || {};
    var container = outer_container;
    container.empty();

    if (opts.abs_positioned !== undefined && opts.abs_positioned) {
        // Create some additional containers to facilitate absolutely
        // positioned spinners.
        var container_id = container.attr('id');
        var inner_container = $('

');
        container.append(inner_container);
        container = inner_container;
        inner_container = $('

');
        container.append(inner_container);
        container = inner_container;
    }

    var spinner_elem = $('

');
    container.append(spinner_elem);
    var text_width = 0;

    if (opts.text !== undefined && opts.text !== '') {
        var text_elem = $('');
        text_elem.text(opts.text);
        container.append(text_elem);
        // See note, below
        text_width = 20 + text_elem.width();
    }

    // These width calculations are tied to the spinner width and
    // margins defined via CSS
    //
    // TODO: We set white-space to 'nowrap' because under some
    // unknown circumstances (it happens on Keegan's laptop) the text
    // width calculation, above, returns a result that's a few pixels
    // too small.  The container's div will be slightly too small,
    // but that's probably OK for our purposes.
    container.css({width: 38 + text_width,
                   height: 38});
    outer_container.css({display: 'block',
                         'white-space': 'nowrap'});

    var spinner = new Spinner({
        lines: 8,
        length: 0,
        width: 9,
        radius: 9,
        speed: 1.25,
        shadow: false,
        zIndex: 1000
    }).spin(spinner_elem[0]);
    outer_container.data("spinner_obj", spinner);
    outer_container.data("destroying", false);

    // Make the spinner appear in the center of its enclosing
    // element.  spinner.el is a 0x0 div.  The parts of the spinner
    // are arranged so that they're centered on the upper-left corner
    // of spinner.el.  So, by setting spinner.el's position to
    // relative and top/left to 50%, the center of the spinner will
    // be located at the center of spinner_elem.
    $(spinner.el).css({left: '50%', top: '50%'});
};

exports.destroy_loading_indicator = function (container) {
    if (container.data("destroying")) {
        return;
    }
    container.data("destroying", true);

    var spinner = container.data("spinner_obj");
    if (spinner !== undefined) {
        spinner.stop();
    }
    container.removeData("spinner_obj");
    container.empty();
    container.css({width: 0, height: 0, display: 'none'});
};

exports.show_first_run_message = function () {
    $('#first_run_message').show();
};

exports.destroy_first_run_message = function () {
    // A no-op if the element no longer exists
    $('#first_run_message').remove();
};

// Like C++'s std::lower_bound.  Returns the first index at which
// `value` could be inserted without changing the ordering.  Assumes
// the array is sorted.
//
// `first` and `last` are indices and `less` is an optionally-specified
// function that returns true if
//   array[i] < value
// for some i and false otherwise.
//
// Usage: lower_bound(array, value, [less])
//        lower_bound(array, first, last, value, [less])
exports.lower_bound = function (array, arg1, arg2, arg3, arg4) {
    var first, last, value, less;
    if (arg3 === undefined) {
        first = 0;
        last = array.length;
        value = arg1;
        less = arg2;
    } else {
        first = arg1;
        last = arg2;
        value = arg3;
        less = arg4;
    }

    if (less === undefined) {
        less = function (a, b) { return a  0) {
        step = Math.floor(len / 2);
        middle = first + step;
        if (less(array[middle], value)) {
            first = middle;
            first++;
            len = len - step - 1;
        }
        else {
            len = step;
        }
    }
    return first;
};

exports.same_stream_and_subject = function util_same_stream_and_subject(a, b) {
    // Streams and subjects are case-insensitive.
    return ((a.stream.toLowerCase() === b.stream.toLowerCase()) &&
            (a.subject.toLowerCase() === b.subject.toLowerCase()));
};

exports.same_major_recipient = function (a, b) {
    // Same behavior as same_recipient, except that it returns true for messages
    // on different topics but the same stream.
    if ((a === undefined) || (b === undefined)) {
        return false;
    }
    if (a.type !== b.type) {
        return false;
    }

    switch (a.type) {
    case 'private':
        return a.reply_to === b.reply_to;
    case 'stream':
        return a.stream.toLowerCase() === b.stream.toLowerCase();
    }

    // should never get here
    return false;
};

exports.same_recipient = function util_same_recipient(a, b) {
    if ((a === undefined) || (b === undefined)) {
        return false;
    }
    if (a.type !== b.type) {
        return false;
    }

    switch (a.type) {
    case 'private':
        return a.reply_to === b.reply_to;
    case 'stream':
        return exports.same_stream_and_subject(a, b);
    }

    // should never get here
    return false;
};

exports.recipient_key = function (message) {
    if (message.type === 'stream') {
        return message.stream.toLowerCase() + ">" + message.subject.toLowerCase();
    } else {
        return exports.normalize_recipients(message.reply_to);
    }
};

exports.same_sender = function util_same_sender(a, b) {
    return ((a !== undefined) && (b !== undefined) &&
            (a.sender_email === b.sender_email));
};

exports.normalize_recipients = function (recipients) {
    // Converts a string listing emails of message recipients
    // into a canonical formatting: emails sorted ASCIIbetically
    // with exactly one comma and no spaces between each.
    recipients = _.map(recipients.split(','), $.trim);
    recipients = _.filter(recipients, function (s) { return s.length > 0; });
    recipients.sort();
    return recipients.join(',');
};

// Avoid URI decode errors by removing characters from the end
// one by one until the decode succeeds.  This makes sense if
// we are decoding input that the user is in the middle of
// typing.
exports.robust_uri_decode = function (str) {
    var end = str.length;
    while (end > 0) {
        try {
            return decodeURIComponent(str.substring(0, end));
        } catch (e) {
            if (!(e instanceof URIError)) {
                throw e;
            }
            end--;
        }
    }
    return '';
};

// If we can, use a locale-aware sorter.  However, if the browser
// doesn't support the ECMAScript Internationalization API
// Specification, do a dumb string comparison because
// String.localeCompare is really slow.
exports.strcmp = (function () {
    try {
        var collator = new Intl.Collator();
        return collator.compare;
    } catch (e) {
    }

    return function util_strcmp (a, b) {
        return (a  b ? 1 : 0));
    };
}());

exports.escape_regexp = function (string) {
    // code from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
    // Modified to escape the ^ to appease jslint. :/
    return string.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
};

exports.array_compare = function util_array_compare(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    var i;
    for (i = 0; i < a.length; ++i) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};

exports.xhr_error_message = function (message, xhr) {
    if (xhr.status.toString().charAt(0) === "4") {
        // Only display the error response for 4XX, where we've crafted
        // a nice response.
        message += ": " + $.parseJSON(xhr.responseText).msg;
    }
    return message;
};

/* Represents a value that is expensive to compute and should be
 * computed on demand and then cached.  The value can be forcefully
 * recalculated on the next call to get() by calling reset().
 *
 * You must supply a option to the constructor called compute_value
 * which should be a function that computes the uncached value.
 */
var unassigned_value_sentinel = {};
exports.CachedValue = function (opts) {
    this._value = unassigned_value_sentinel;
    _.extend(this, opts);
};

exports.CachedValue.prototype = {
    get: function CachedValue_get() {
        if (this._value === unassigned_value_sentinel) {
            this._value = this.compute_value();
        }
        return this._value;
    },

    reset: function CachedValue_reset() {
        this._value = unassigned_value_sentinel;
    }
};

exports.enforce_arity = function util_enforce_arity(func) {
    return function () {
        if (func.length !== arguments.length) {
            throw new Error("Function '" + func.name + "' called with "
                            + arguments.length + " arguments, but expected "
                            + func.length);
        }
        return func.apply(this, arguments);
    };
};

if (typeof $ !== 'undefined') {
    $.fn.expectOne = function () {
        if (blueslip && this.length !== 1) {
            blueslip.error("Expected one element in jQuery set, " + this.length + " found");
        }
        return this;
    };
}

exports.execute_early = function (func) {
    if (page_params.test_suite) {
        $(document).one('phantom_page_loaded', func);
    } else {
        $(func);
    }
};

return exports;
}());
if (typeof module !== 'undefined') {
    module.exports = util;
}
<commit_msg>Pass index of element to util.lower_bound matching function<commit_after>var util = (function () {

var exports = {};

// From MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
exports.random_int = function random_int(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

var favicon_selector = 'link[rel="shortcut icon"]';

// We need to reset the favicon after changing the
// window.location.hash or Firefox will drop the favicon.  See
// https://bugzilla.mozilla.org/show_bug.cgi?id=519028
exports.reset_favicon = function () {
    $(favicon_selector).detach().appendTo('head');
};

exports.set_favicon = function (url) {
    if ($.browser.webkit) {
        // Works in Chrome 22 at least.
        // Doesn't work in Firefox 10.
        $(favicon_selector).attr('href', url);
    } else {
        // Delete and re-create the node.
        // May cause excessive work by the browser
        // in re-rendering the page (see #882).
        $(favicon_selector).remove();
        $('head').append($('<link>')
            .attr('rel',  'shortcut icon')
            .attr('href', url));
    }
};

exports.make_loading_indicator = function (outer_container, opts) {
    opts = opts || {};
    var container = outer_container;
    container.empty();

    if (opts.abs_positioned !== undefined && opts.abs_positioned) {
        // Create some additional containers to facilitate absolutely
        // positioned spinners.
        var container_id = container.attr('id');
        var inner_container = $('

');
        container.append(inner_container);
        container = inner_container;
        inner_container = $('

');
        container.append(inner_container);
        container = inner_container;
    }

    var spinner_elem = $('

');
    container.append(spinner_elem);
    var text_width = 0;

    if (opts.text !== undefined && opts.text !== '') {
        var text_elem = $('');
        text_elem.text(opts.text);
        container.append(text_elem);
        // See note, below
        text_width = 20 + text_elem.width();
    }

    // These width calculations are tied to the spinner width and
    // margins defined via CSS
    //
    // TODO: We set white-space to 'nowrap' because under some
    // unknown circumstances (it happens on Keegan's laptop) the text
    // width calculation, above, returns a result that's a few pixels
    // too small.  The container's div will be slightly too small,
    // but that's probably OK for our purposes.
    container.css({width: 38 + text_width,
                   height: 38});
    outer_container.css({display: 'block',
                         'white-space': 'nowrap'});

    var spinner = new Spinner({
        lines: 8,
        length: 0,
        width: 9,
        radius: 9,
        speed: 1.25,
        shadow: false,
        zIndex: 1000
    }).spin(spinner_elem[0]);
    outer_container.data("spinner_obj", spinner);
    outer_container.data("destroying", false);

    // Make the spinner appear in the center of its enclosing
    // element.  spinner.el is a 0x0 div.  The parts of the spinner
    // are arranged so that they're centered on the upper-left corner
    // of spinner.el.  So, by setting spinner.el's position to
    // relative and top/left to 50%, the center of the spinner will
    // be located at the center of spinner_elem.
    $(spinner.el).css({left: '50%', top: '50%'});
};

exports.destroy_loading_indicator = function (container) {
    if (container.data("destroying")) {
        return;
    }
    container.data("destroying", true);

    var spinner = container.data("spinner_obj");
    if (spinner !== undefined) {
        spinner.stop();
    }
    container.removeData("spinner_obj");
    container.empty();
    container.css({width: 0, height: 0, display: 'none'});
};

exports.show_first_run_message = function () {
    $('#first_run_message').show();
};

exports.destroy_first_run_message = function () {
    // A no-op if the element no longer exists
    $('#first_run_message').remove();
};

// Like C++'s std::lower_bound.  Returns the first index at which
// `value` could be inserted without changing the ordering.  Assumes
// the array is sorted.
//
// `first` and `last` are indices and `less` is an optionally-specified
// function that returns true if
//   array[i] < value
// for some i and false otherwise.
//
// Usage: lower_bound(array, value, [less])
//        lower_bound(array, first, last, value, [less])
exports.lower_bound = function (array, arg1, arg2, arg3, arg4) {
    var first, last, value, less;
    if (arg3 === undefined) {
        first = 0;
        last = array.length;
        value = arg1;
        less = arg2;
    } else {
        first = arg1;
        last = arg2;
        value = arg3;
        less = arg4;
    }

    if (less === undefined) {
        less = function (a, b) { return a  0) {
        step = Math.floor(len / 2);
        middle = first + step;
        if (less(array[middle], value, middle)) {
            first = middle;
            first++;
            len = len - step - 1;
        }
        else {
            len = step;
        }
    }
    return first;
};

exports.same_stream_and_subject = function util_same_stream_and_subject(a, b) {
    // Streams and subjects are case-insensitive.
    return ((a.stream.toLowerCase() === b.stream.toLowerCase()) &&
            (a.subject.toLowerCase() === b.subject.toLowerCase()));
};

exports.same_major_recipient = function (a, b) {
    // Same behavior as same_recipient, except that it returns true for messages
    // on different topics but the same stream.
    if ((a === undefined) || (b === undefined)) {
        return false;
    }
    if (a.type !== b.type) {
        return false;
    }

    switch (a.type) {
    case 'private':
        return a.reply_to === b.reply_to;
    case 'stream':
        return a.stream.toLowerCase() === b.stream.toLowerCase();
    }

    // should never get here
    return false;
};

exports.same_recipient = function util_same_recipient(a, b) {
    if ((a === undefined) || (b === undefined)) {
        return false;
    }
    if (a.type !== b.type) {
        return false;
    }

    switch (a.type) {
    case 'private':
        return a.reply_to === b.reply_to;
    case 'stream':
        return exports.same_stream_and_subject(a, b);
    }

    // should never get here
    return false;
};

exports.recipient_key = function (message) {
    if (message.type === 'stream') {
        return message.stream.toLowerCase() + ">" + message.subject.toLowerCase();
    } else {
        return exports.normalize_recipients(message.reply_to);
    }
};

exports.same_sender = function util_same_sender(a, b) {
    return ((a !== undefined) && (b !== undefined) &&
            (a.sender_email === b.sender_email));
};

exports.normalize_recipients = function (recipients) {
    // Converts a string listing emails of message recipients
    // into a canonical formatting: emails sorted ASCIIbetically
    // with exactly one comma and no spaces between each.
    recipients = _.map(recipients.split(','), $.trim);
    recipients = _.filter(recipients, function (s) { return s.length > 0; });
    recipients.sort();
    return recipients.join(',');
};

// Avoid URI decode errors by removing characters from the end
// one by one until the decode succeeds.  This makes sense if
// we are decoding input that the user is in the middle of
// typing.
exports.robust_uri_decode = function (str) {
    var end = str.length;
    while (end > 0) {
        try {
            return decodeURIComponent(str.substring(0, end));
        } catch (e) {
            if (!(e instanceof URIError)) {
                throw e;
            }
            end--;
        }
    }
    return '';
};

// If we can, use a locale-aware sorter.  However, if the browser
// doesn't support the ECMAScript Internationalization API
// Specification, do a dumb string comparison because
// String.localeCompare is really slow.
exports.strcmp = (function () {
    try {
        var collator = new Intl.Collator();
        return collator.compare;
    } catch (e) {
    }

    return function util_strcmp (a, b) {
        return (a  b ? 1 : 0));
    };
}());

exports.escape_regexp = function (string) {
    // code from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
    // Modified to escape the ^ to appease jslint. :/
    return string.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
};

exports.array_compare = function util_array_compare(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    var i;
    for (i = 0; i < a.length; ++i) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};

exports.xhr_error_message = function (message, xhr) {
    if (xhr.status.toString().charAt(0) === "4") {
        // Only display the error response for 4XX, where we've crafted
        // a nice response.
        message += ": " + $.parseJSON(xhr.responseText).msg;
    }
    return message;
};

/* Represents a value that is expensive to compute and should be
 * computed on demand and then cached.  The value can be forcefully
 * recalculated on the next call to get() by calling reset().
 *
 * You must supply a option to the constructor called compute_value
 * which should be a function that computes the uncached value.
 */
var unassigned_value_sentinel = {};
exports.CachedValue = function (opts) {
    this._value = unassigned_value_sentinel;
    _.extend(this, opts);
};

exports.CachedValue.prototype = {
    get: function CachedValue_get() {
        if (this._value === unassigned_value_sentinel) {
            this._value = this.compute_value();
        }
        return this._value;
    },

    reset: function CachedValue_reset() {
        this._value = unassigned_value_sentinel;
    }
};

exports.enforce_arity = function util_enforce_arity(func) {
    return function () {
        if (func.length !== arguments.length) {
            throw new Error("Function '" + func.name + "' called with "
                            + arguments.length + " arguments, but expected "
                            + func.length);
        }
        return func.apply(this, arguments);
    };
};

if (typeof $ !== 'undefined') {
    $.fn.expectOne = function () {
        if (blueslip && this.length !== 1) {
            blueslip.error("Expected one element in jQuery set, " + this.length + " found");
        }
        return this;
    };
}

exports.execute_early = function (func) {
    if (page_params.test_suite) {
        $(document).one('phantom_page_loaded', func);
    } else {
        $(func);
    }
};

return exports;
}());
if (typeof module !== 'undefined') {
    module.exports = util;
}
<|endoftext|>