<commit_before>/**
 * log.js is a "Poor man's" logging module.  It provides basic colorized logging using named
 * loggers with selectable log levels.
 */
var process = require('process');
try { var colors = require('colors'); } catch(e) {var colors = false;}
var fs = require('fs');
var path = require('path');
var jsesc = require('jsesc');
var _suppress = false;
var log_buffer = [];
var LOG_BUFFER_SIZE = 5000;
var PERSISTENT_LOG_COUNT = 20;

// String versions of the allowable log levels
LEVELS = {
	'g2' : 0,
	'debug' : 1,
	'info' : 2,
	'warn' : 3,
	'error' : 4,
};

// Default log levels for loggers with specific names.
LOG_LEVELS = {
	'g2' : 'debug',
	'gcode' : 'debug',
	'sbp' : 'debug',
	'machine' : 'debug',
	'manual' : 'debug',
	'api' : 'debug',
	'detection' :'debug',
	'config_loader' : 'debug',
	'settings' : 'debug',
	'log':'debug'
};

var FlightRecorder = function() {
  this.records = []
  this.firstTime = 0
  this.info = {
    'startTime' : new Date().toISOString(),
    'arch' : process.arch,
    'platform' : process.platform
  }
}

FlightRecorder.prototype.record = function(channel, dir, data) {
  // Get the current time
  var t = new Date().getTime();

  // Add the current message to the record
  this.records.push({
    'time' : t,
    'data' : data,
		'dir' : dir,
    'channel' : channel
  })
}

FlightRecorder.prototype.getLatest = function() {
	if(this.records.length === 0) { return null; }
	return this.records[this.records.length-1];
}

FlightRecorder.prototype.save = function(filename, callback) {
	var newRecords = []
	if(this.records.length > 0) {
		var startTime = this.records[0].time;
		this.records.forEach(function(record) {
			newRecords.push({
				't' : record.time-startTime,
				'ch' : record.channel,
				'dir' : record.dir,
				'data' : record.data, /*new Buffer(record.data).toString('base64')*/
			});
		}.bind(this));
	}
  this.info.endTime = new Date().toISOString();
	var flight = {
		records : newRecords,
	    info : this.info
    }
  fs.writeFile(filename, JSON.stringify(flight, null, 2), callback);
}

var flightRecorder = new FlightRecorder();

function setGlobalLevel(lvl){
	if (lvl)
	{
		if (lvl >= 0 && lvl <= 3)
		{
			// assign the log level to the string equivalent of the integer
			Object.keys(LOG_LEVELS).forEach(function(key) {
	  			LOG_LEVELS[key] = Object.keys(LEVELS).filter(function(key) {return (LEVELS[key] === lvl);})[0];
	  		});
		}
		else if (Object.keys(LEVELS).indexOf(lvl) >= 0) // if a string
		{
			//  assign the log level to the string that is given
			Object.keys(LOG_LEVELS).forEach(function(key) {
	  			LOG_LEVELS[key] = lvl;
	  		});
		}
		else if (lvl === "none")
		{
			return;
		}
		else
		{
			logger('log').warn('Invalid log level: ' + lvl);
		}
	}
}

// The Logger object is what is created and used by modules to log to the console.
var Logger = function(name) {
	this.name = name;
};

// Index of all the Logger objects that have been created.
var logs = {};

// Output the provided message with colorized output (if available) and the logger name
Logger.prototype.write = function(level, msg) {
	if(_suppress) {
		return;
	}

	my_level = LOG_LEVELS[this.name] || 'debug';
	if((LEVELS[level] || 0) >= (LEVELS[my_level] || 0)) {
		buffer_msg = level + ': ' + msg + ' ['+this.name+']';
		if(colors) {
			switch(level) {
				case 'g2':
					console.log((level + ': ').magenta + msg + ' ['+this.name+']');
					break;
				case 'debug':
					console.log((level + ': ').blue + msg+' ['+this.name+']');
					break;
				case 'info':
					console.log((level + ': ').green + msg+' ['+this.name+']');
					break;
				case 'warn':
					console.log((level + ': ').yellow + msg+' ['+this.name+']');
					break;
				case 'error':
					console.log((level + ': ').red + msg+' ['+this.name+']');
					break;
			}
		} else {
			console.log(level + ': ' + msg+' ['+this.name+']');
		}
		log_buffer.push(buffer_msg);
		while(log_buffer.length > LOG_BUFFER_SIZE) {
			log_buffer.shift();
		}
	}
};

// These functions provide a shorthand alternative to specifying the log level every time
Logger.prototype.debug = function(msg) { this.write('debug', msg);};
Logger.prototype.stack = function(msg) {
	var stackTrace = new Error().stack;
	stackTrace = stackTrace.split('\n');
	stackTrace = stackTrace.slice(2).join('\n');
	this.write('debug', 'Stack Trace:\n' + stackTrace);
}

Logger.prototype.info = function(msg) { this.write('info', msg);};
Logger.prototype.warn = function(msg) { this.write('warn', msg);};
Logger.prototype.error = function(msg) {
	if(msg && msg.stack) {
		this.write('error', msg.stack);
	} else {
		this.write('error', msg);
	}
};

Logger.prototype.g2 = function(channel, dir, msg) {
	flightRecorder.record(channel, dir, msg);
	msg = flightRecorder.getLatest();
	switch(dir) {
		case 'out':
			this.write('g2', '--' + msg.channel + '-' + msg.time + '----> ' + jsesc(msg.data.trim()));
			break;
		case 'in':
			this.write('g2', '<-' + msg.channel + '-' + msg.time + '----- ' + jsesc(msg.data.trim()));
			break;
	}
};

Logger.prototype.uncaught = function(err) {
	if(colors) {
		console.log("UNCAUGHT EXCEPTION".red.underline);
		console.log(('' + err.stack).red)
	} else {
		console.log("UNCAUGHT EXCEPTION");
		console.log(err.stack);
	}
	log_buffer.push("UNCAUGHT EXCEPTION");
	log_buffer.push(err.stack);
}

// Factory function for producing a new, named logger object
var logger = function(name) {
	if(name in logs) {
		return logs[name];
	} else {
		l = new Logger(name);
		logs[name] = l;
		return l;
	}
};

// Logging internal to the logging module - the logger log logger log
//
// .... log log logger log.
var _log = logger('log');

function exitHandler(options, err) {
    options = options || {};
    if (err) {
    	_log.uncaught(err);
    }
    var dir = require('./config').getDataDir('log')
    var fn = 'fabmo-' + Date.now() + '-log.txt'
		var flight_fn = path.join(dir, 'g2-flight-log.json');

    filename = path.join(dir, fn)
    if(options.savelog) {
    	_log.info("Saving log...")
    	try {
	    	saveLogBuffer(filename);
	    	_log.info("Log saved to " + filename);
				flightRecorder.save(flight_fn, function(err, data) {
					rotateLogs(PERSISTENT_LOG_COUNT, function() {
		    		if(options.exit) {
		    			process.exit();
		    		}
		    	});
				});
	    	return;
    	} catch(e) {
	    	_log.error("Could not save log to " + filename);
	    	_log.error(e);
    	}
	    if (options.exit) {
	    	process.exit();
	    }
	}
}

process.on('exit', exitHandler.bind(null));
process.on('SIGINT', exitHandler.bind(null, {savelog:true, exit:true}));
process.on('uncaughtException', exitHandler.bind(null, {savelog:true, exit:false}));

var suppress = function(v) {_suppress = true;};
var unsuppress = function(v) {_suppress = false;};

var getLogBuffer = function() {
	return log_buffer.join('\n');
};

var clearLogBuffer = function() {
	log_buffer = [];
}

var saveLogBuffer = function(filename) {
	fs.writeFileSync(filename, getLogBuffer());
}

var rotateLogs = function(count,callback) {
	var logdir = require('./config').getDataDir('log');
	callback = callback || function() {};
	try {
		fs.readdir(logdir, function(err, files) {
			files.sort();
			if(files.length <= count) {
				return callback();
			}
			var filesToDelete = files.slice(0, files.length-count);
			async.each(
				filesToDelete,
				function(file, callback) {
					fs.unlink(path.join(logdir, file), callback)
				},
				function(err) {
					if(err) {
						_log.error(err);
					} else {
			    		_log.info(filesToDelete.length + " old logfile removed.");
					}
					callback(null)
				});
			});
	} catch(e) {
		_log.error(e);
		callback(e);
	}
}

exports.FlightRecorder = FlightRecorder;
exports.suppress = suppress;
exports.logger = logger;
exports.setGlobalLevel = setGlobalLevel;
exports.getLogBuffer = getLogBuffer;
exports.clearLogBuffer = clearLogBuffer;
exports.rotateLogs = rotateLogs;
<commit_msg>Make flight recorder optional - only runs when at "g2" log level<commit_after>/**
 * log.js is a "Poor man's" logging module.  It provides basic colorized logging using named
 * loggers with selectable log levels.
 */
var process = require('process');
try { var colors = require('colors'); } catch(e) {var colors = false;}
var fs = require('fs');
var path = require('path');
var jsesc = require('jsesc');
var _suppress = false;
var log_buffer = [];
var LOG_BUFFER_SIZE = 5000;
var PERSISTENT_LOG_COUNT = 20;
var flightRecorder = null;

// String versions of the allowable log levels
LEVELS = {
	'g2' : 0,
	'debug' : 1,
	'info' : 2,
	'warn' : 3,
	'error' : 4,
};

// Default log levels for loggers with specific names.
LOG_LEVELS = {
	'g2' : 'debug',
	'gcode' : 'debug',
	'sbp' : 'debug',
	'machine' : 'debug',
	'manual' : 'debug',
	'api' : 'debug',
	'detection' :'debug',
	'config_loader' : 'debug',
	'settings' : 'debug',
	'log':'debug'
};

var FlightRecorder = function() {
  this.records = []
  this.firstTime = 0
  this.info = {
    'startTime' : new Date().toISOString(),
    'arch' : process.arch,
    'platform' : process.platform
  }
}

FlightRecorder.prototype.record = function(channel, dir, data) {
  // Get the current time
  var t = new Date().getTime();

  // Add the current message to the record
  this.records.push({
    'time' : t,
    'data' : data,
		'dir' : dir,
    'channel' : channel
  })
}

FlightRecorder.prototype.getLatest = function() {
	if(this.records.length === 0) { return null; }
	return this.records[this.records.length-1];
}

FlightRecorder.prototype.save = function(filename, callback) {
	var newRecords = []
	if(this.records.length > 0) {
		var startTime = this.records[0].time;
		this.records.forEach(function(record) {
			newRecords.push({
				't' : record.time-startTime,
				'ch' : record.channel,
				'dir' : record.dir,
				'data' : record.data, /*new Buffer(record.data).toString('base64')*/
			});
		}.bind(this));
	}
  this.info.endTime = new Date().toISOString();
	var flight = {
		records : newRecords,
	    info : this.info
    }
  fs.writeFile(filename, JSON.stringify(flight, null, 2), callback);
}

function setGlobalLevel(lvl){
	if (lvl) {
		if (lvl >= 0 && lvl <= 3) {
			// assign the log level to the string equivalent of the integer
			Object.keys(LOG_LEVELS).forEach(function(key) {
	  			LOG_LEVELS[key] = Object.keys(LEVELS).filter(function(key) {return (LEVELS[key] === lvl);})[0];
	  		});
		}
		else if (Object.keys(LEVELS).indexOf(lvl) >= 0) {
			//  assign the log level to the string that is given
			Object.keys(LOG_LEVELS).forEach(function(key) {
	  			LOG_LEVELS[key] = lvl;
	  		});
		}
		else if (lvl === "none")
		{
			return;
		}
		else
		{
			logger('log').warn('Invalid log level: ' + lvl);
		}
		if(lvl === "g2") {
			_log.info("Creating flight recorder...")
			flightRecorder = new FlightRecorder();
		} else {
			if(flightRecorder) {
				_log.info("Destroying flight recorder...")
			}
			flightRecorder = null;
		}
	}
}

// The Logger object is what is created and used by modules to log to the console.
var Logger = function(name) {
	this.name = name;
};

// Index of all the Logger objects that have been created.
var logs = {};

// Output the provided message with colorized output (if available) and the logger name
Logger.prototype.write = function(level, msg) {
	if(_suppress) {
		return;
	}

	my_level = LOG_LEVELS[this.name] || 'debug';
	if((LEVELS[level] || 0) >= (LEVELS[my_level] || 0)) {
		buffer_msg = level + ': ' + msg + ' ['+this.name+']';
		if(colors) {
			switch(level) {
				case 'g2':
					console.log((level + ': ').magenta + msg + ' ['+this.name+']');
					break;
				case 'debug':
					console.log((level + ': ').blue + msg+' ['+this.name+']');
					break;
				case 'info':
					console.log((level + ': ').green + msg+' ['+this.name+']');
					break;
				case 'warn':
					console.log((level + ': ').yellow + msg+' ['+this.name+']');
					break;
				case 'error':
					console.log((level + ': ').red + msg+' ['+this.name+']');
					break;
			}
		} else {
			console.log(level + ': ' + msg+' ['+this.name+']');
		}
		log_buffer.push(buffer_msg);
		while(log_buffer.length > LOG_BUFFER_SIZE) {
			log_buffer.shift();
		}
	}
};

// These functions provide a shorthand alternative to specifying the log level every time
Logger.prototype.debug = function(msg) { this.write('debug', msg);};
Logger.prototype.stack = function(msg) {
	var stackTrace = new Error().stack;
	stackTrace = stackTrace.split('\n');
	stackTrace = stackTrace.slice(2).join('\n');
	this.write('debug', 'Stack Trace:\n' + stackTrace);
}

Logger.prototype.info = function(msg) { this.write('info', msg);};
Logger.prototype.warn = function(msg) { this.write('warn', msg);};
Logger.prototype.error = function(msg) {
	if(msg && msg.stack) {
		this.write('error', msg.stack);
	} else {
		this.write('error', msg);
	}
};

Logger.prototype.g2 = function(channel, dir, o) {
  var msg = {}
	if(flightRecorder) {
		flightRecorder.record(channel, dir, o);
		msg = flightRecorder.getLatest();
	} else {
		msg.channel = channel;
		msg.data = o;
		msg.time = new Date().getTime();
	}
	switch(dir) {
		case 'out':
			this.write('g2', '--' + msg.channel + '-' + msg.time + '----> ' + jsesc(msg.data.trim()));
			break;
		case 'in':
			this.write('g2', '<-' + msg.channel + '-' + msg.time + '----- ' + jsesc(msg.data.trim()));
			break;
	}
};

Logger.prototype.uncaught = function(err) {
	if(colors) {
		console.log("UNCAUGHT EXCEPTION".red.underline);
		console.log(('' + err.stack).red)
	} else {
		console.log("UNCAUGHT EXCEPTION");
		console.log(err.stack);
	}
	log_buffer.push("UNCAUGHT EXCEPTION");
	log_buffer.push(err.stack);
}

// Factory function for producing a new, named logger object
var logger = function(name) {
	if(name in logs) {
		return logs[name];
	} else {
		l = new Logger(name);
		logs[name] = l;
		return l;
	}
};

// Logging internal to the logging module - the logger log logger log
//
// .... log log logger log.
var _log = logger('log');

function exitHandler(options, err) {
    options = options || {};
    if (err) {
    	_log.uncaught(err);
    }
    var dir = require('./config').getDataDir('log')
    var fn = 'fabmo-' + Date.now() + '-log.txt'
		var flight_fn = path.join(dir, 'g2-flight-log.json');

    filename = path.join(dir, fn)
    if(options.savelog) {
    	_log.info("Saving log...")
    	try {
	    	saveLogBuffer(filename);
	    	_log.info("Log saved to " + filename);
				if(flightRecorder) {
					flightRecorder.save(flight_fn, function(err, data) {
						rotateLogs(PERSISTENT_LOG_COUNT, function() {
			    		if(options.exit) {
			    			process.exit();
			    		}
			    	});
					});
				}
	    	return;
    	} catch(e) {
	    	_log.error("Could not save log to " + filename);
	    	_log.error(e);
    	}
	    if (options.exit) {
	    	process.exit();
	    }
	}
}

process.on('exit', exitHandler.bind(null));
process.on('SIGINT', exitHandler.bind(null, {savelog:true, exit:true}));
process.on('uncaughtException', exitHandler.bind(null, {savelog:true, exit:false}));

var suppress = function(v) {_suppress = true;};
var unsuppress = function(v) {_suppress = false;};

var getLogBuffer = function() {
	return log_buffer.join('\n');
};

var clearLogBuffer = function() {
	log_buffer = [];
}

var saveLogBuffer = function(filename) {
	fs.writeFileSync(filename, getLogBuffer());
}

var rotateLogs = function(count,callback) {
	var logdir = require('./config').getDataDir('log');
	callback = callback || function() {};
	try {
		fs.readdir(logdir, function(err, files) {
			files.sort();
			if(files.length <= count) {
				return callback();
			}
			var filesToDelete = files.slice(0, files.length-count);
			async.each(
				filesToDelete,
				function(file, callback) {
					fs.unlink(path.join(logdir, file), callback)
				},
				function(err) {
					if(err) {
						_log.error(err);
					} else {
			    		_log.info(filesToDelete.length + " old logfile removed.");
					}
					callback(null)
				});
			});
	} catch(e) {
		_log.error(e);
		callback(e);
	}
}

exports.FlightRecorder = FlightRecorder;
exports.suppress = suppress;
exports.logger = logger;
exports.setGlobalLevel = setGlobalLevel;
exports.getLogBuffer = getLogBuffer;
exports.clearLogBuffer = clearLogBuffer;
exports.rotateLogs = rotateLogs;
<|endoftext|>


<commit_before>// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
  if (~tab.url.indexOf('http://mapstack.stamen.com/edit.html')) {
      chrome.tabs.executeScript(tab.id, {
        code: "\
        var container = document.querySelector('#map'), \
            imgs = [].slice.call(container.querySelectorAll('img.leaflet-tile.leaflet-tile-loaded')); \
        [imgs.map(function(img) { return [img.src, img.style.left, img.style.top, img.offsetWidth, img.offsetHeight]; }), container.offsetWidth, container.offsetHeight]; \
        "
      }, function(ans) {
        ans = ans[0];
        var imgs = ans[0],
            canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d'),
            counter = imgs.length;

        canvas.width = ans[1];
        canvas.height = ans[2];

        var paint = function(img, x, y, width, height) {
          counter--;
          ctx.drawImage(img, x, y, width, height);
          if (!counter) {
            chrome.tabs.update(tab.id, {url: canvas.toDataURL()});
          }
        }

        for (var i = 0, l = imgs.length; i < l; ++i) {
          var img = new Image(),
              imgData = imgs[i],
              src = imgData[0],
              x = parseInt(imgData[1], 10),
              y = parseInt(imgData[2], 10),
              width = imgData[3],
              height = imgData[4];

          img.src = src;
          img.onload = paint.bind(img, img, x, y, width, height);
        }
      });
  }
});

<commit_msg>Updated to handle big data URLs<commit_after>// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {

  //based on https://github.com/mrcoles/full-page-screen-capture-chrome-extension/blob/master/popup.js
  //MIT license https://raw.github.com/mrcoles/full-page-screen-capture-chrome-extension/master/LICENSE
  function openPage(url, canvas) {
    // standard dataURI can be too big, let's blob instead
    // http://code.google.com/p/chromium/issues/detail?id=69227#c27

    var dataURI = canvas.toDataURL();

    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs
    var byteString = atob(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to an ArrayBuffer
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }

    // create a blob for writing to a file
    var blob = new Blob([ab], {type: mimeString});

    // come up with a filename
    var name = url.split('?')[0].split('#')[0];
    if (name) {
      name = name
      .replace(/^https?:\/\//, '')
      .replace(/[^A-z0-9]+/g, '-')
      .replace(/-+/g, '-')
      .replace(/^[_\-]+/, '')
      .replace(/[_\-]+$/, '');
      name = '-' + name;
    } else {
      name = '';
    }
    name = 'screencapture' + name + '.png';

    function onwriteend() {
      // open the file that now contains the blob
      window.open('filesystem:chrome-extension://' + chrome.i18n.getMessage("@@extension_id") + '/temporary/' + name);
    }

    function errorHandler() {
      console.log('something wrong happened');
    }

    // create a blob for writing to a file
    window.webkitRequestFileSystem(TEMPORARY, 1024*1024, function(fs){
      fs.root.getFile(name, {create:true}, function(fileEntry) {
        fileEntry.createWriter(function(fileWriter) {
          fileWriter.onwriteend = onwriteend;
          fileWriter.write(blob);
        }, errorHandler);
      }, errorHandler);
    }, errorHandler);
  }

  if (~tab.url.indexOf('http://mapstack.stamen.com/edit.html')) {
      chrome.tabs.executeScript(tab.id, {
        code: "\
        var container = document.querySelector('#map'), \
            imgs = [].slice.call(container.querySelectorAll('img.leaflet-tile.leaflet-tile-loaded')); \
        [imgs.map(function(img) { return [img.src, img.style.left, img.style.top, img.offsetWidth, img.offsetHeight]; }), container.offsetWidth, container.offsetHeight]; \
        "
      }, function(ans) {
        console.log('executed');
        ans = ans[0];
        var imgs = ans[0],
            canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d'),
            counter = imgs.length;

        canvas.width = ans[1];
        canvas.height = ans[2];

        var paint = function(img, x, y, width, height) {
          counter--;
          ctx.drawImage(img, x, y, width, height);
          if (!counter) {
            console.log('open page');
            openPage(tab.url, canvas);
          }
        };

        var handleError = function() {
          counter--;
          console.log('error ' + this.src);
        };

        for (var i = 0, l = imgs.length; i < l; ++i) {
          var img = new Image(),
              imgData = imgs[i],
              src = imgData[0],
              x = parseInt(imgData[1], 10),
              y = parseInt(imgData[2], 10),
              width = imgData[3],
              height = imgData[4];

          img.src = src;
          img.onload = paint.bind(img, img, x, y, width, height);
          img.onerror = handleError.bind(img);
        }
      });
  }
});

<|endoftext|>


<commit_before>currentX = 0;

function drawMap(mapString, mapWidth, mapHeight, tileSize) {
    for (currentTile = 0; currentTile < mapWidth * mapHeight; currentTile++) {

        if (currentX < mapWidth - 1) {
            currentX++;
        } else {
            currentX = 0;
        }

        if (mapString.charAt(currentTile) == 0) {
            canvas.fillStyle = colorGreen;
            canvas.fillRect(currentX * tileSize * setup.scale, Math.floor(currentTile / mapWidth) * tileSize * setup.scale, tileSize * setup.scale, tileSize * setup.scale);
        }
    }
}<commit_msg>Fixed the tilemap system.<commit_after>function drawMap(mapString, mapWidth, mapHeight, tileSize) {
    for (currentTile = 0; currentTile < mapWidth * mapHeight; currentTile++) {
        if (mapString.charAt(currentTile) == 0) {
            canvas.fillStyle = colorGreen;
            canvas.fillRect((currentTile - (Math.floor(currentTile / mapWidth) * mapWidth)) * tileSize * setup.scale, Math.floor(currentTile / mapWidth) * tileSize * setup.scale, tileSize * setup.scale, tileSize * setup.scale);
        }
    }
}<|endoftext|>


<commit_before>// Create the Visualization
var map = d3.select('#map');

// Read the width from the element and the height from the screen object
var width = map.node().clientWidth,
    height = screen.height;

// Create the SVG map
var svg = map
          .append('svg')
          .attr('width', width)
          .attr('height', height);

var projection, path;

var counter = 0;

var routesCache, routeLookup;

var loaded = false;
var timeWindow = 15000;
var progress = 0;

var hiddenLayers = [],
    layers       = [];

var promises = {};

['streets', 'arteries', 'freeways', 'neighborhoods'].forEach(function (file){
  promises[file] = d3.promise.json('./'+file+'.json');
});

// now load them in order
promises.streets.then(function (json){

  // Use the street dataset to workout the projection bounds
  workoutProjection(json);

  promises.neighborhoods
    .then(function (json2){
    appendPaths('neighborhoods', json2);
  }).then(function(){

    appendPaths('streets', json);

    return promises.arteries;
  }).then(function (json){

    appendPaths('arteries', json);

    return promises.freeways;

  }).then(function (json){

    appendPaths('freeways', json);

    // append an element to store the paths and the vehicles
    svg
      .append('g')
      .classed('paths', true);
    svg
      .append('g')
      .classed('routes', true);

    setupBusTracking();

    bindUI();
  });

});

function updateProgress(delta){
  progress += delta;
  // just in case
  progress = Math.min(100, progress);

  var completed = progress === 100;

  var el = d3
    .select('#progress-bar')
    .style('width', progress + '%')
    .text(!completed ? progress + '%' : 'Completed!');

  if(completed){
    // its a bit brutal but it's ok for now
    // setTimeout(function(){
      d3.select('#progress-data').transition().duration(1000).style('opacity', '0');
    // }, 1000);
  }
}

function bindUI(){
  d3.selectAll('.layer-filter').on('click', doFiltering);

  var debounceKeyboard = debounce(onKeyUp, 50);

  d3.select('#routeId').on('input', debounceKeyboard, false);
}

// Simple debounce function from
// http://stackoverflow.com/questions/9400615/whats-the-best-way-to-make-a-d3-js-visualisation-layout-responsive
function debounce(fn, timeout) {
  var timeoutID = -1;
  return function() {
     if (timeoutID > -1) {
        window.clearTimeout(timeoutID);
     }
   timeoutID = window.setTimeout(fn, timeout);
  };
}

function onKeyUp(){
  var text = d3.select('#routeId')[0][0].value || '';
  // clean the string
  var routes = text.toUpperCase().split(' ');
  // now create a lookup object
  var lookup = {};
  routes.forEach(function (route){
    lookup[route] = 1;
  });

  var any = routes.some(function (route){
    return route in routeLookup;
  });

  if( any ){
    // highlight the route here
    highlightLines(lookup);

  } else {
    restoreLines();
  }
}

function doFiltering(){
  var layer = d3.select(this).text().toLowerCase();
  // toggle the layer here
  if(hiddenLayers.indexOf(layer) > -1){
    // remove from the list
    hiddenLayers = hiddenLayers.filter(function (l){
      return l !== layer;
    });
  } else {
    hiddenLayers.push(layer);
  }
  filterLayer();
}

function filterLayer(){
  // for each layer
  layers.forEach(function (layerId){
    d3.select('#'+layerId).classed('hidden', function (){
      return hiddenLayers.indexOf(layerId) > -1;
    });
  });
}

function workoutProjection(geoJSON){
  var center = d3.geo.centroid(geoJSON);
  var scale  = 150;
  var offset = [width/2, height/2];
  projection = d3.geo.mercator().scale(scale).center(center)
      .translate(offset);

  // create the path
  path = d3.geo.path().projection(projection);

  // using the path determine the bounds of the current map and use
  // these to determine better values for the scale and translation
  var bounds  = path.bounds(geoJSON);
  var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
  var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
  scale   = (hscale < vscale) ? hscale : vscale;
  offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                    height - (bounds[0][1] + bounds[1][1])/2];

  // new projection
  projection = d3.geo.mercator().center(center)
    .scale(scale).translate(offset);
  path = path.projection(projection);
}

function appendPaths(type, json){

  var track = svg
     .append('g').attr('id', type)
     .selectAll("path")
     .data(json.features)
     .enter()
     .append('path')
     .attr('d', path);

  layers.push(type);

  updateProgress(15);
}

function getVehiclePositions(time){

  var max = -Infinity,
      min = Infinity;
  var positions = [];

  return routesCache
    .map(function (route){
      return d3.promise.xml('http://webservices.nextbus.com/service/publicXMLFeed?command=vehicleLocations&a=sf-muni&r='+route.id+'&t='+(time || 0));
    }).reduce(function (chain, promise){
      return chain.then(function(){
        return promise;
      })
      .then(function (data){
        var linePos = getPositions(data);

        positions = positions.concat(linePos);

        var time = + d3.select(data).select('lastTime').attr('time');

        max = Math.max(max, time);
        min = Math.min(min, time);
      });
    }, Promise.resolve())
    .then(function(){

      return {avg: min + (max - min), positions: positions };
    });
}

function getPositions(data){
  var points = [];

  d3.select(data)
    .selectAll('vehicle')
    .each(function(){
      var vehicle = d3.select(this);

      points.push({
        type: 'Feature',
        properties:{
          id: vehicle.attr('id'),
          route: vehicle.attr('routeTag')
        },
        geometry:  {
          type: 'Point',
          coordinates: [ + vehicle.attr('lon'), + vehicle.attr('lat')]
        }
      });
    });

  return points;
}

function drawDots(geoJSON){

  // update elements as needed
  var dots = svg.select('g.routes').selectAll('g.routeDots')
            .data(geoJSON.features, function (d){ return d.properties.id; });

  // add new dots if needed
  dots.enter().append('g')
      .classed('routeDots', true)
      // Place them in the center, so that the animation to the specific point is nicer
      .attr('transform', function (d){
        return 'translate('+(width/2)+','+(height/2)+')';
      });

  // Now update the positions
  dots.transition('linear').duration(loaded ? timeWindow - 500 : 1000)
      .attr('transform', function (d){
        var projPos = projection(d.geometry.coordinates);
        return 'translate('+projPos[0]+','+projPos[1]+')';
      });

  // remove elements as needed
  // dots.exit().remove();

  if(!loaded){

    dots
      .append('circle')
      .attr('r', 7)
      .style('fill', function(d){
        return '#' + (routeLookup[d.properties.route] && routeLookup[d.properties.route].color || 'FFFFFF');
      });

    dots
      .append('text')
      .text(function(d){ return d.properties.route; });

    // Do not make people hover a 5px circle... bind the g element
    svg.selectAll('g.routeDots').on('mouseover', function (d){
      var id = d.properties.id;
      var route = d.properties.route;
      svg.selectAll('g.routeDots').each(function (p){
        d3.select(this)
          .classed('active', p.properties.id === id)
          .classed('hidden', p.properties.id !== id);
      });
      svg.selectAll('g.paths path').classed('active', function (p){
        //match by route tag
        return p.id === route;
      });
    });

    svg.selectAll('g.routeDots circle').on('mouseleave', restoreLines);
  }
  loaded = true;

  return dots;
}

function highlightLines(lookup){
  svg.selectAll('g.routeDots').each(function (p){
    d3.select(this)
      .classed('active', p.properties.route in lookup)
      .classed('hidden', !(p.properties.route in lookup))
      .attr('r', p.properties.route in lookup ? 15 : 7);
  });
  svg.selectAll('g.paths path').classed('active', function (p){
    //match by route tag
    return p.id in lookup;
  });
}

function restoreLines(){
  // remove all highlights
  svg.selectAll('g.routeDots').classed('active hidden', false).attr('r', 7);
  svg.selectAll('g.paths path').classed('active', false);
}

function setupBusTracking(){

  var routesList = d3.promise.xml('http://webservices.nextbus.com/service/publicXMLFeed?command=routeList&a=sf-muni');
  // This request does paging every 100 routes: sf muni has 84 lines so it should be fine
  var routesPaths = d3.promise.xml('http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni');

  routesList
    .then(function (data){

      routesCache = getRoutes(data);

      routeLookup = {};

      routesCache.forEach(function (route){
        routeLookup[route.id] = route;
      });

      updateProgress(10);

      return routesPaths;
    })
    .then(function (data){

      doRoutesPaths(data);

      updateProgress(7);

      createRouteLegend();
      updateProgress(3);

      return getVehiclePositions();
    })
    .then(function (busInfo){

      updateProgress(30);

      updateMap(busInfo);
    });
}

function updateMap(busInfo){

  drawDots({type: 'FeatureCollection', features: busInfo.positions});

  setTimeout(function(){
    getVehiclePositions(busInfo.avg)
      .then(function (busInfo){
        updateMap(busInfo);
      });
  }, timeWindow);
}

function getRoutes(data){
  var routes = [];
  d3.select(data)
    .selectAll('route')
    .each(function(){
      var node = d3.select(this);
      routes.push({id: node.attr('tag'), name: node.attr('title') });
    });
  return routes;
}

function doRoutesPaths(data){
  var paths = [];

  d3.select(data)
    .selectAll('route')
    .each(function(){
      var pathLines = [];
      var node = d3.select(this);
      // get the color of the route
      var color = node.attr('color');
      var id    = node.attr('tag');

      routeLookup[id].color = color;

      // format the paths in something d3 can understand
      node
        .selectAll('path')
        .each(function(){
          var feature = [];

          d3.select(this).selectAll('point').each(function(){
            var point = d3.select(this);
            feature.push([ + point.attr('lon'), + point.attr('lat'), 0]);
          });

          pathLines.push({type: 'Feature', id: id, geometry: {type: 'LineString', coordinates: feature }});
        });

      paths.push({id: id, color: color, paths: pathLines});
      // now get all the paths for the given route
    });

  var pathLines = paths.map(function (route){
    return route.paths;
  }).reduce(function (features, paths){

    return features.concat(paths);

  }, []);

  svg.select('g.paths')
     .selectAll('path')
     .data(pathLines)
     .enter()
     .append('path')
     .attr('d', path)
     .style('stroke', function (d){ return '#'+routeLookup[d.id].color; });

  return paths;
}

function createRouteLegend(){
  var legend = d3.select('#legend').append('svg')
              .attr('width', 450)
              .attr('height', 450)
              .selectAll('g')
     .data(routesCache, function(d){ return d.id; })
     .enter()
     .append('g')
     .classed('legendDots', true)
     .attr('transform', function (_, i){
       // arrange the dots in a grid
       return 'translate('+(i%10 * 40 + 30)+','+(30 + 40 * Math.floor(i / 10))+')';
     })
     .on('mouseover', function (d){
      var dict = {};
      dict[d.id] = 1;
      highlightLines(dict);
     }).on('mouseleave', restoreLines);

  legend.append('circle')
      .attr('r', 15)
      .style('fill', function (d){
        return '#' + (routeLookup[d.id] && routeLookup[d.id].color || 'FFFFFF');
      });

  legend.append('text').text(function(d){ return d.id; });
}
<commit_msg>Add zoom and pan<commit_after>// Create the Visualization
var map = d3.select('#map');

// Read the width from the element and the height from the screen object
var width = map.node().clientWidth,
    height = screen.height;

// Create the SVG map
var svg = map
          .append('svg')
          .attr('width', width)
          .attr('height', height)
          // Add zoom and pan
          .call(d3.behavior.zoom().on("zoom", function () {
            svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")")
          }))
          .append('g').attr('id', '#container');

var projection, path;

var counter = 0;

var routesCache, routeLookup;

var loaded = false;
var timeWindow = 15000;
var progress = 0;

var hiddenLayers = [],
    layers       = [];

var promises = {};

['streets', 'arteries', 'freeways', 'neighborhoods'].forEach(function (file){
  promises[file] = d3.promise.json('./'+file+'.json');
});

// now load them in order
promises.streets.then(function (json){

  // Use the street dataset to workout the projection bounds
  workoutProjection(json);

  promises.neighborhoods
    .then(function (json2){
    appendPaths('neighborhoods', json2);
  }).then(function(){

    appendPaths('streets', json);

    return promises.arteries;
  }).then(function (json){

    appendPaths('arteries', json);

    return promises.freeways;

  }).then(function (json){

    appendPaths('freeways', json);

    // append an element to store the paths and the vehicles
    svg
      .append('g')
      .classed('paths', true);
    svg
      .append('g')
      .classed('routes', true);

    setupBusTracking();

    bindUI();
  });

});

function updateProgress(delta){
  progress += delta;
  // just in case
  progress = Math.min(100, progress);

  var completed = progress === 100;

  var el = d3
    .select('#progress-bar')
    .style('width', progress + '%')
    .text(!completed ? progress + '%' : 'Completed!');

  if(completed){
    // its a bit brutal but it's ok for now
    d3.select('#progress-data').transition().duration(1000).style('opacity', '0');
  }
}

function bindUI(){
  d3.selectAll('.layer-filter').on('click', doFiltering);

  var debounceKeyboard = debounce(onKeyUp, 50);

  d3.select('#routeId')
    .on('input', debounceKeyboard, false);
}

// Simple debounce function from
// http://stackoverflow.com/questions/9400615/whats-the-best-way-to-make-a-d3-js-visualisation-layout-responsive
function debounce(fn, timeout) {
  var timeoutID = -1;
  return function() {
     if (timeoutID > -1) {
        window.clearTimeout(timeoutID);
     }
   timeoutID = window.setTimeout(fn, timeout);
  };
}

function onKeyUp(){
  var text = d3.select('#routeId')[0][0].value || '';
  // clean the string
  var routes = text.toUpperCase().split(' ');
  // now create a lookup object
  var lookup = {};
  routes.forEach(function (route){
    lookup[route] = 1;
  });

  var any = routes.some(function (route){
    return route in routeLookup;
  });

  if( any ){
    // highlight the route here
    highlightLines(lookup);

  } else {
    restoreLines();
  }

}

function doFiltering(){
  var layer = d3.select(this).text().toLowerCase();
  // toggle the layer here
  if(hiddenLayers.indexOf(layer) > -1){
    // remove from the list
    hiddenLayers = hiddenLayers.filter(function (l){
      return l !== layer;
    });
  } else {
    hiddenLayers.push(layer);
  }
  filterLayer();
}

function filterLayer(){
  // for each layer
  layers.forEach(function (layerId){
    d3.select('#'+layerId).classed('hidden', function (){
      return hiddenLayers.indexOf(layerId) > -1;
    });
  });
}

function workoutProjection(geoJSON){
  var center = d3.geo.centroid(geoJSON);
  var scale  = 150;
  var offset = [width/2, height/2];
  projection = d3.geo.mercator().scale(scale).center(center)
      .translate(offset);

  // create the path
  path = d3.geo.path().projection(projection);

  // using the path determine the bounds of the current map and use
  // these to determine better values for the scale and translation
  var bounds  = path.bounds(geoJSON);
  var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
  var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
  scale   = (hscale < vscale) ? hscale : vscale;
  offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                    height - (bounds[0][1] + bounds[1][1])/2];

  // new projection
  projection = d3.geo.mercator().center(center)
    .scale(scale).translate(offset);
  path = path.projection(projection);
}

function appendPaths(type, json){

  var track = svg
     .append('g').attr('id', type)
     .selectAll("path")
     .data(json.features)
     .enter()
     .append('path')
     .attr('d', path);

  layers.push(type);

  updateProgress(15);
}

function getVehiclePositions(time){

  var max = -Infinity,
      min = Infinity;
  var positions = [];

  return routesCache
    .map(function (route){
      return d3.promise.xml('http://webservices.nextbus.com/service/publicXMLFeed?command=vehicleLocations&a=sf-muni&r='+route.id+'&t='+(time || 0));
    }).reduce(function (chain, promise){
      return chain.then(function(){
        return promise;
      })
      .then(function (data){
        var linePos = getPositions(data);

        positions = positions.concat(linePos);

        var time = + d3.select(data).select('lastTime').attr('time');

        max = Math.max(max, time);
        min = Math.min(min, time);
      });
    }, Promise.resolve())
    .then(function(){

      return {avg: min + (max - min), positions: positions };
    });
}

function getPositions(data){
  var points = [];

  d3.select(data)
    .selectAll('vehicle')
    .each(function(){
      var vehicle = d3.select(this);

      points.push({
        type: 'Feature',
        properties:{
          id: vehicle.attr('id'),
          route: vehicle.attr('routeTag')
        },
        geometry:  {
          type: 'Point',
          coordinates: [ + vehicle.attr('lon'), + vehicle.attr('lat')]
        }
      });
    });

  return points;
}

function drawDots(geoJSON){

  // update elements as needed
  var dots = svg.select('g.routes').selectAll('g.routeDots')
            .data(geoJSON.features, function (d){ return d.properties.id; });

  // add new dots if needed
  dots.enter().append('g')
      .classed('routeDots', true)
      // Place them in the center, so that the animation to the specific point is nicer
      .attr('transform', function (d){
        return 'translate('+(width/2)+','+(height/2)+')';
      });

  // Now update the positions
  dots.transition('linear').duration(loaded ? timeWindow - 500 : 1000)
      .attr('transform', function (d){
        var projPos = projection(d.geometry.coordinates);
        return 'translate('+projPos[0]+','+projPos[1]+')';
      });

  // remove elements as needed
  // dots.exit().remove();

  if(!loaded){

    dots
      .append('circle')
      .attr('r', 7)
      .style('fill', function(d){
        return '#' + (routeLookup[d.properties.route] && routeLookup[d.properties.route].color || 'FFFFFF');
      });

    dots
      .append('text')
      .text(function(d){ return d.properties.route; });

    // Do not make people hover a 5px circle... bind the g element
    svg.selectAll('g.routeDots').on('mouseenter', function (d){
      var id = d.properties.id;
      var route = d.properties.route;
      svg.selectAll('g.routeDots').each(function (p){
        d3.select(this)
          .classed('active', p.properties.id === id)
          .classed('hidden', p.properties.id !== id);
      });
      svg.selectAll('g.paths path').classed('active', function (p){
        //match by route tag
        return p.id === route;
      });
    });

    svg.selectAll('g.routeDots circle').on('mouseleave', restoreLines);
  }
  loaded = true;

  return dots;
}

function highlightLines(lookup){
  svg.selectAll('g.routeDots').each(function (p){
    d3.select(this)
      .classed('active', p.properties.route in lookup)
      .classed('hidden', !(p.properties.route in lookup))
      .attr('r', p.properties.route in lookup ? 15 : 7);
  });
  svg.selectAll('g.paths path').classed('active', function (p){
    //match by route tag
    return p.id in lookup;
  });
}

function restoreLines(){
  // remove all highlights
  svg.selectAll('g.routeDots').classed('active hidden', false).attr('r', 7);
  svg.selectAll('g.paths path').classed('active', false);
}

function getRouteList(){
  return d3.promise
    .xml('http://webservices.nextbus.com/service/publicXMLFeed?command=routeList&a=sf-muni')
    .catch(function(){
      console.log(arguments);
    });
}

function getRoutePaths(){
  return d3.promise
    .xml('http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni')
    .catch(function(){
      console.log(arguments);
    });
}

function setupBusTracking(){

  var routesList = getRouteList();
  // This request does paging every 100 routes: sf muni has 84 lines so it should be fine
  var routesPaths = getRoutePaths();

  routesList
    .then(function (data){

      routesCache = getRoutes(data);

      routeLookup = {};

      routesCache.forEach(function (route){
        routeLookup[route.id] = route;
      });

      updateProgress(10);

      return routesPaths;
    })
    .then(function (data){

      doRoutesPaths(data);

      updateProgress(7);

      createRouteLegend();
      updateProgress(3);

      return getVehiclePositions();
    })
    .then(function (busInfo){

      updateProgress(30);

      updateMap(busInfo);
    });
}

function updateMap(busInfo){

  drawDots({type: 'FeatureCollection', features: busInfo.positions});

  setTimeout(function(){
    getVehiclePositions(busInfo.avg)
      .then(function (busInfo){
        updateMap(busInfo);
      });
  }, timeWindow);
}

function getRoutes(data){
  var routes = [];
  d3.select(data)
    .selectAll('route')
    .each(function(){
      var node = d3.select(this);
      routes.push({id: node.attr('tag'), name: node.attr('title') });
    });
  return routes;
}

function doRoutesPaths(data){
  var paths = [];

  d3.select(data)
    .selectAll('route')
    .each(function(){
      var pathLines = [];
      var node = d3.select(this);
      // get the color of the route
      var color = node.attr('color');
      var id    = node.attr('tag');

      routeLookup[id].color = color;

      // format the paths in something d3 can understand
      node
        .selectAll('path')
        .each(function(){
          var feature = [];

          d3.select(this).selectAll('point').each(function(){
            var point = d3.select(this);
            feature.push([ + point.attr('lon'), + point.attr('lat'), 0]);
          });

          pathLines.push({type: 'Feature', id: id, geometry: {type: 'LineString', coordinates: feature }});
        });

      paths.push({id: id, color: color, paths: pathLines});
      // now get all the paths for the given route
    });

  var pathLines = paths.map(function (route){
    return route.paths;
  }).reduce(function (features, paths){

    return features.concat(paths);

  }, []);

  svg.select('g.paths')
     .selectAll('path')
     .data(pathLines)
     .enter()
     .append('path')
     .attr('d', path)
     .style('stroke', function (d){ return '#'+routeLookup[d.id].color; });

  return paths;
}

function createRouteLegend(){
  var legend = d3.select('#legend').append('svg')
              .attr('width', 450)
              .attr('height', 450)
              .selectAll('g')
     .data(routesCache, function(d){ return d.id; })
     .enter()
     .append('g')
     .classed('legendDots', true)
     .attr('transform', function (_, i){
       // arrange the dots in a grid
       return 'translate('+(i%10 * 40 + 30)+','+(30 + 40 * Math.floor(i / 10))+')';
     })
     .on('mouseover', function (d){
      var dict = {};
      dict[d.id] = 1;
      highlightLines(dict);
     }).on('mouseleave', restoreLines);

  legend.append('circle')
      .attr('r', 15)
      .style('fill', function (d){
        return '#' + (routeLookup[d.id] && routeLookup[d.id].color || 'FFFFFF');
      });

  legend.append('text').text(function(d){ return d.id; });
}
<|endoftext|>


<commit_before>// ;; #path=Mappable #kind=interface
// There are various things that positions can be mapped through.
// We'll denote those as 'mappable'. This is not an actual class in
// the codebase, only an agreed-on interface.

// :: (pos: number, bias: ?number) → number #path=Mappable.map
// Map a position through this object. When given, `bias` (should be
// -1 or 1) determines in which direction to move when a chunk of
// content is inserted at or around the mapped position.

// :: (pos: number, bias: ?number) → MapResult #path=Mappable.mapResult
// Map a position, and return an object containing additional
// information about the mapping. The result's `deleted` field tells
// you whether the position was deleted (completely enclosed in a
// replaced range) during the mapping.

// Recovery values encode a range index and an offset. They are
// represented as numbers, because tons of them will be created when
// mapping, for example, a large number of marked ranges. The number's
// lower 16 bits provide the index, the remaining bits the offset.
//
// Note: We intentionally don't use bit shift operators to en- and
// decode these, since those clip to 32 bits, which we might in rare
// cases want to overflow. A 64-bit float can represent 48-bit
// integers precisely.

const lower16 = 0xffff
const factor16 = Math.pow(2, 16)

function makeRecover(index, offset) { return index + offset * factor16 }
function recoverIndex(value) { return value & lower16 }
function recoverOffset(value) { return (value - (value & lower16)) / factor16 }

// ;; An object representing a mapped position with some extra
// information.
class MapResult {
  constructor(pos, deleted = false, recover = null) {
    // :: number The mapped version of the position.
    this.pos = pos
    // :: bool Tells you whether the position was deleted, that is,
    // whether the step removed its surroundings from the document.
    this.deleted = deleted
    this.recover = recover
  }
}
exports.MapResult = MapResult

// ;; A position map, holding information about the way positions in
// the pre-step version of a document correspond to positions in the
// post-step version. This class implements `Mappable`.
class PosMap {
  // :: ([number])
  // Create a position map. The modifications to the document are
  // represented as an array of numbers, in which each group of three
  // represents a modified chunk as `[start, oldSize, newSize]`.
  constructor(ranges, inverted = false) {
    this.ranges = ranges
    this.inverted = inverted
  }

  recover(value) {
    let diff = 0, index = recoverIndex(value)
    if (!this.inverted) for (let i = 0; i < index; i++)
      diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1]
    return this.ranges[index * 3] + diff + recoverOffset(value)
  }

  // :: (number, ?number) → MapResult
  // Map the given position through this map. The `bias` parameter can
  // be used to control what happens when the transform inserted
  // content at (or around) this position—if `bias` is negative, the a
  // position before the inserted content will be returned, if it is
  // positive, a position after the insertion is returned.
  mapResult(pos, bias) { return this._map(pos, bias, false) }

  // :: (number, ?number) → number
  // Map the given position through this map, returning only the
  // mapped position.
  map(pos, bias) { return this._map(pos, bias, true) }

  _map(pos, bias, simple) {
    let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
    for (let i = 0; i < this.ranges.length; i += 3) {
      let start = this.ranges[i] - (this.inverted ? diff : 0)
      if (start > pos) break
      let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize
      if (pos <= end) {
        let side = !oldSize ? bias : pos == start ? -1 : pos == end ? 1 : bias
        let result = start + diff + (side < 0 ? 0 : newSize)
        if (simple) return result
        let recover = makeRecover(i / 3, pos - start)
        return new MapResult(result, pos != start && pos != end, recover)
      }
      diff += newSize - oldSize
    }
    return simple ? pos + diff : new MapResult(pos + diff)
  }

  touches(pos, recover) {
    let diff = 0, index = recoverIndex(recover)
    let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
    for (let i = 0; i < this.ranges.length; i += 3) {
      let start = this.ranges[i] - (this.inverted ? diff : 0)
      if (start > pos) break
      let oldSize = this.ranges[i + oldIndex], end = start + oldSize
      if (pos <= end && i == index * 3) return true
      diff += this.ranges[i + newIndex] - oldSize
    }
    return false
  }

  // :: () → PosMap
  // Create an inverted version of this map. The result can be used to
  // map positions in the post-step document to the pre-step document.
  invert() {
    return new PosMap(this.ranges, !this.inverted)
  }

  toString() {
    return (this.inverted ? "-" : "") + JSON.stringify(this.ranges)
  }
}
exports.PosMap = PosMap

PosMap.empty = new PosMap([])

// ;; A remapping represents a pipeline of zero or more mappings. It
// is a specialized data structured used to manage mapping through a
// series of steps, typically including inverted and non-inverted
// versions of the same step. (This comes up when ‘rebasing’ steps for
// collaboration or history management.) This class implements
// `Mappable`.
class Remapping {
  // :: (?[PosMap], ?number)
  // Create a new remapping with the given position maps, with its
  // current start index pointing at `mapFrom`.
  constructor(maps, mapFrom = 0, mirror) {
    // :: [PosMap]
    this.maps = maps || []
    // :: number
    // The current starting position in the `maps` array, used when
    // `map` or `mapResult` is called.
    this.mapFrom = mapFrom
    this.mirror = mirror
  }

  getMirror(n) {
    if (this.mirror) for (let i = 0; i < this.mirror.length; i++)
      if (this.mirror[i] == n) return this.mirror[i + (i % 2 ? -1 : 1)]
  }

  setMirror(n, m) {
    if (!this.mirror) this.mirror = []
    this.mirror.push(n, m)
  }

  // :: (PosMap, ?number)
  // Add a map to the end of this remapping. If `mirrors` is given, it
  // should be the index of the map that is the mirror image of this
  // one.
  appendMap(map, mirrors) {
    this.maps.push(map)
    if (mirrors != null) this.setMirror(this.maps.length - 1, mirrors)
  }

  // :: (number, ?number) → MapResult
  // Map a position through this remapping, returning a mapping
  // result.
  mapResult(pos, bias) { return this._map(pos, bias, false) }

  // :: (number, ?number) → number
  // Map a position through this remapping.
  map(pos, bias) {
    if (this.mirror) return this._map(pos, bias, true)
    for (let i = this.mapFrom; i < this.maps.length; i++)
      pos = this.maps[i].map(pos, bias)
    return pos
  }

  _map(pos, bias, simple) {
    let deleted = false, recoverables = null

    for (let i = this.mapFrom; i < this.maps.length; i++) {
      let map = this.maps[i], rec = recoverables && recoverables[i]
      if (rec != null && map.touches(pos, rec)) {
        pos = map.recover(rec)
        continue
      }

      let result = map.mapResult(pos, bias)
      if (result.recover != null) {
        let corr = this.getMirror(i)
        if (corr != null && corr > i) {
          if (result.deleted) {
            i = corr
            pos = this.maps[corr].recover(result.recover)
            continue
          } else {
            ;(recoverables || (recoverables = Object.create(null)))[corr] = result.recover
          }
        }
      }

      if (result.deleted) deleted = true
      pos = result.pos
    }

    return simple ? pos : new MapResult(pos, deleted)
  }
}
exports.Remapping = Remapping

// :: ([Mappable], number, ?number, ?number) → number
// Map the given position through an array of mappables. When `start`
// is given, the mapping is started at that array position.
function mapThrough(mappables, pos, bias, start) {
  for (let i = start || 0; i < mappables.length; i++)
    pos = mappables[i].map(pos, bias)
  return pos
}
exports.mapThrough = mapThrough

// :: ([Mappable], number, ?number, ?number) → MapResult
// Map the given position through an array of mappables, returning a
// `MapResult` object.
function mapThroughResult(mappables, pos, bias, start) {
  let deleted = false
  for (let i = start || 0; i < mappables.length; i++) {
    let result = mappables[i].mapResult(pos, bias)
    pos = result.pos
    if (result.deleted) deleted = true
  }
  return new MapResult(pos, deleted)
}
exports.mapThroughResult = mapThroughResult

class MapThrough {
  constructor(mappables, start) {
    this.mappables = mappables
    this.start = start || 0
  }

  map(pos, bias) {
    return mapThrough(this.mappables, pos, bias, this.start)
  }

  mapResult(pos, bias) {
    return mapThroughResult(this.mappables, pos, bias, this.start)
  }
}
exports.MapThrough = MapThrough
<commit_msg>Add a force argument to coordsAtPos and posAtCoords<commit_after>// ;; #path=Mappable #kind=interface
// There are various things that positions can be mapped through.
// We'll denote those as 'mappable'. This is not an actual class in
// the codebase, only an agreed-on interface.

// :: (pos: number, bias: ?number) → number #path=Mappable.map
// Map a position through this object. When given, `bias` (should be
// -1 or 1) determines in which direction to move when a chunk of
// content is inserted at or around the mapped position.

// :: (pos: number, bias: ?number) → MapResult #path=Mappable.mapResult
// Map a position, and return an object containing additional
// information about the mapping. The result's `deleted` field tells
// you whether the position was deleted (completely enclosed in a
// replaced range) during the mapping.

// Recovery values encode a range index and an offset. They are
// represented as numbers, because tons of them will be created when
// mapping, for example, a large number of marked ranges. The number's
// lower 16 bits provide the index, the remaining bits the offset.
//
// Note: We intentionally don't use bit shift operators to en- and
// decode these, since those clip to 32 bits, which we might in rare
// cases want to overflow. A 64-bit float can represent 48-bit
// integers precisely.

const lower16 = 0xffff
const factor16 = Math.pow(2, 16)

function makeRecover(index, offset) { return index + offset * factor16 }
function recoverIndex(value) { return value & lower16 }
function recoverOffset(value) { return (value - (value & lower16)) / factor16 }

// ;; An object representing a mapped position with some extra
// information.
class MapResult {
  constructor(pos, deleted = false, recover = null) {
    // :: number The mapped version of the position.
    this.pos = pos
    // :: bool Tells you whether the position was deleted, that is,
    // whether the step removed its surroundings from the document.
    this.deleted = deleted
    this.recover = recover
  }
}
exports.MapResult = MapResult

// ;; A position map, holding information about the way positions in
// the pre-step version of a document correspond to positions in the
// post-step version. This class implements `Mappable`.
class PosMap {
  // :: ([number])
  // Create a position map. The modifications to the document are
  // represented as an array of numbers, in which each group of three
  // represents a modified chunk as `[start, oldSize, newSize]`.
  constructor(ranges, inverted = false) {
    this.ranges = ranges
    this.inverted = inverted
  }

  recover(value) {
    let diff = 0, index = recoverIndex(value)
    if (!this.inverted) for (let i = 0; i < index; i++)
      diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1]
    return this.ranges[index * 3] + diff + recoverOffset(value)
  }

  // :: (number, ?number) → MapResult
  // Map the given position through this map. The `bias` parameter can
  // be used to control what happens when the transform inserted
  // content at (or around) this position—if `bias` is negative, the a
  // position before the inserted content will be returned, if it is
  // positive, a position after the insertion is returned.
  mapResult(pos, bias) { return this._map(pos, bias, false) }

  // :: (number, ?number) → number
  // Map the given position through this map, returning only the
  // mapped position.
  map(pos, bias) { return this._map(pos, bias, true) }

  _map(pos, bias, simple) {
    let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
    for (let i = 0; i < this.ranges.length; i += 3) {
      let start = this.ranges[i] - (this.inverted ? diff : 0)
      if (start > pos) break
      let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize
      if (pos <= end) {
        let side = !oldSize ? bias : pos == start ? -1 : pos == end ? 1 : bias
        let result = start + diff + (side < 0 ? 0 : newSize)
        if (simple) return result
        let recover = makeRecover(i / 3, pos - start)
        return new MapResult(result, pos != start && pos != end, recover)
      }
      diff += newSize - oldSize
    }
    return simple ? pos + diff : new MapResult(pos + diff)
  }

  touches(pos, recover) {
    let diff = 0, index = recoverIndex(recover)
    let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
    for (let i = 0; i < this.ranges.length; i += 3) {
      let start = this.ranges[i] - (this.inverted ? diff : 0)
      if (start > pos) break
      let oldSize = this.ranges[i + oldIndex], end = start + oldSize
      if (pos <= end && i == index * 3) return true
      diff += this.ranges[i + newIndex] - oldSize
    }
    return false
  }

  // :: () → PosMap
  // Create an inverted version of this map. The result can be used to
  // map positions in the post-step document to the pre-step document.
  invert() {
    return new PosMap(this.ranges, !this.inverted)
  }

  toString() {
    return (this.inverted ? "-" : "") + JSON.stringify(this.ranges)
  }
}
exports.PosMap = PosMap

PosMap.empty = new PosMap([])

// ;; A remapping represents a pipeline of zero or more mappings. It
// is a specialized data structured used to manage mapping through a
// series of steps, typically including inverted and non-inverted
// versions of the same step. (This comes up when ‘rebasing’ steps for
// collaboration or history management.) This class implements
// `Mappable`.
class Remapping {
  // :: (?[PosMap], ?number)
  // Create a new remapping with the given position maps, with its
  // current start index pointing at `mapFrom`.
  constructor(maps, mapFrom = 0, mirror) {
    // :: [PosMap]
    this.maps = maps || []
    // :: number
    // The current starting position in the `maps` array, used when
    // `map` or `mapResult` is called.
    this.mapFrom = mapFrom
    this.mirror = mirror
  }

  getMirror(n) {
    if (this.mirror) for (let i = 0; i < this.mirror.length; i++)
      if (this.mirror[i] == n) return this.mirror[i + (i % 2 ? -1 : 1)]
  }

  setMirror(n, m) {
    if (!this.mirror) this.mirror = []
    this.mirror.push(n, m)
  }

  // :: (PosMap, ?number)
  // Add a map to the end of this remapping. If `mirrors` is given, it
  // should be the index of the map that is the mirror image of this
  // one.
  appendMap(map, mirrors) {
    this.maps.push(map)
    if (mirrors != null) this.setMirror(this.maps.length - 1, mirrors)
  }

  // :: (number, ?number) → MapResult
  // Map a position through this remapping, returning a mapping
  // result.
  mapResult(pos, bias) { return this._map(pos, bias, false) }

  // :: (number, ?number) → number
  // Map a position through this remapping.
  map(pos, bias) {
    if (this.mirror) return this._map(pos, bias, true)
    for (let i = this.mapFrom; i < this.maps.length; i++)
      pos = this.maps[i].map(pos, bias)
    return pos
  }

  _map(pos, bias, simple) {
    let deleted = false, recoverables = null

    for (let i = this.mapFrom; i < this.maps.length; i++) {
      let map = this.maps[i], rec = recoverables && recoverables[i]
      if (rec != null && map.touches(pos, rec)) {
        pos = map.recover(rec)
        continue
      }

      let result = map.mapResult(pos, bias)
      if (result.recover != null) {
        let corr = this.getMirror(i)
        if (corr != null && corr > i) {
          if (result.deleted) {
            i = corr
            pos = this.maps[corr].recover(result.recover)
            continue
          } else {
            ;(recoverables || (recoverables = Object.create(null)))[corr] = result.recover
          }
        }
      }

      if (result.deleted) deleted = true
      pos = result.pos
    }

    return simple ? pos : new MapResult(pos, deleted)
  }
}
exports.Remapping = Remapping

// :: ([Mappable], number, ?number, ?number) → number
// Map the given position through an array of mappables. When `start`
// is given, the mapping is started at that array position.
function mapThrough(mappables, pos, bias, start = 0, end = mappables.length) {
  for (let i = start; i < end; i++)
    pos = mappables[i].map(pos, bias)
  return pos
}
exports.mapThrough = mapThrough

// :: ([Mappable], number, ?number, ?number) → MapResult
// Map the given position through an array of mappables, returning a
// `MapResult` object.
function mapThroughResult(mappables, pos, bias, start = 0, end = mappables.length) {
  let deleted = false
  for (let i = start; i < end; i++) {
    let result = mappables[i].mapResult(pos, bias)
    pos = result.pos
    if (result.deleted) deleted = true
  }
  return new MapResult(pos, deleted)
}
exports.mapThroughResult = mapThroughResult

class MapThrough {
  constructor(mappables, start = 0, end = mappables.length) {
    this.mappables = mappables
    this.start = start
    this.end = end
  }

  map(pos, bias) {
    return mapThrough(this.mappables, pos, bias, this.start, this.end)
  }

  mapResult(pos, bias) {
    return mapThroughResult(this.mappables, pos, bias, this.start, this.end)
  }
}
exports.MapThrough = MapThrough
<|endoftext|>


<commit_before>module.exports = function(api) {
  var validEnv = ['development', 'test', 'production']
  var currentEnv = api.env()
  var isDevelopmentEnv = api.env('development')
  var isProductionEnv = api.env('production')
  var isTestEnv = api.env('test')

  if (!validEnv.includes(currentEnv)) {
    throw new Error(
      'Please specify a valid `NODE_ENV` or ' +
        '`BABEL_ENV` environment variables. Valid values are "development", ' +
        '"test", and "production". Instead, received: ' +
        JSON.stringify(currentEnv) +
        '.'
    )
  }

  return {
    presets: [
      isTestEnv && [
        require('@babel/preset-env').default,
        {
          targets: {
            node: 'current'
          }
        }
      ],
      (isProductionEnv || isDevelopmentEnv) && [
        require('@babel/preset-env').default,
        {
          forceAllTransforms: true,
          useBuiltIns: 'entry',
          modules: false,
          exclude: ['transform-typeof-symbol']
        }
      ]
    ].filter(Boolean),
    plugins: [
      require('babel-plugin-macros'),
      require('@babel/plugin-syntax-dynamic-import').default,
      isTestEnv && require('babel-plugin-dynamic-import-node'),
      require('@babel/plugin-transform-destructuring').default,
      [
        require('@babel/plugin-proposal-class-properties').default,
        {
          loose: true
        }
      ],
      [
        require('@babel/plugin-proposal-object-rest-spread').default,
        {
          useBuiltIns: true
        }
      ],
      [
        require('@babel/plugin-transform-runtime').default,
        {
          helpers: false,
          regenerator: true
        }
      ],
      [
        require('@babel/plugin-transform-regenerator').default,
        {
          async: false
        }
      ]
    ].filter(Boolean)
  }
}
<commit_msg>Add corejs support to suppress WARNING<commit_after>module.exports = function(api) {
  var validEnv = ['development', 'test', 'production']
  var currentEnv = api.env()
  var isDevelopmentEnv = api.env('development')
  var isProductionEnv = api.env('production')
  var isTestEnv = api.env('test')

  if (!validEnv.includes(currentEnv)) {
    throw new Error(
      'Please specify a valid `NODE_ENV` or ' +
        '`BABEL_ENV` environment variables. Valid values are "development", ' +
        '"test", and "production". Instead, received: ' +
        JSON.stringify(currentEnv) +
        '.'
    )
  }

  return {
    presets: [
      isTestEnv && [
        require('@babel/preset-env').default,
        {
          targets: {
            node: 'current'
          }
        }
      ],
      (isProductionEnv || isDevelopmentEnv) && [
        require('@babel/preset-env').default,
        {
          corejs: 2,
          forceAllTransforms: true,
          useBuiltIns: 'entry',
          modules: false,
          exclude: ['transform-typeof-symbol']
        }
      ]
    ].filter(Boolean),
    plugins: [
      require('babel-plugin-macros'),
      require('@babel/plugin-syntax-dynamic-import').default,
      isTestEnv && require('babel-plugin-dynamic-import-node'),
      require('@babel/plugin-transform-destructuring').default,
      [
        require('@babel/plugin-proposal-class-properties').default,
        {
          loose: true
        }
      ],
      [
        require('@babel/plugin-proposal-object-rest-spread').default,
        {
          useBuiltIns: true
        }
      ],
      [
        require('@babel/plugin-transform-runtime').default,
        {
          helpers: false,
          regenerator: true
        }
      ],
      [
        require('@babel/plugin-transform-regenerator').default,
        {
          async: false
        }
      ]
    ].filter(Boolean)
  }
}
<|endoftext|>


<commit_before>module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false,
      },
    ],
    '@babel/preset-react',
  ],
  plugins: [
    'lodash',
    'styled-components',
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-syntax-dynamic-import',
  ],
  env: {
    production: {
      only: ['app'],
      plugins: [
        'transform-react-remove-prop-types',
        '@babel/plugin-transform-react-inline-elements',
        '@babel/plugin-transform-react-constant-elements',
      ],
    },
    test: {
      plugins: [
        '@babel/plugin-transform-modules-commonjs',
        'dynamic-import-node',
      ],
    },
  },
};
<commit_msg>Babel-plugin-lodash only needed in production<commit_after>module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false,
      },
    ],
    '@babel/preset-react',
  ],
  plugins: [
    'styled-components',
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-syntax-dynamic-import',
  ],
  env: {
    production: {
      only: ['app'],
      plugins: [
        'lodash',
        'transform-react-remove-prop-types',
        '@babel/plugin-transform-react-inline-elements',
        '@babel/plugin-transform-react-constant-elements',
      ],
    },
    test: {
      plugins: [
        '@babel/plugin-transform-modules-commonjs',
        'dynamic-import-node',
      ],
    },
  },
};
<|endoftext|>


<commit_before>import React from 'react';

class User extends React.PureComponent {
  render() {
    const email = this.props.email ?  {this.props.email} : null;
    const active = this.props.active || false;
    const activeInfo =  active: {active.toString()};

    return (

        {this.props.firstName}
        {this.props.lastName}
        {email}
        {activeInfo}

    );
  }
}

export default User;
<commit_msg>fixed className of user active span in 03<commit_after>import React from 'react';

class User extends React.PureComponent {
  render() {
    const email = this.props.email ?  {this.props.email} : null;
    const active = this.props.active || false;
    const activeInfo =  active: {active.toString()};

    return (

        {this.props.firstName}
        {this.props.lastName}
        {email}
        {activeInfo}

    );
  }
}

export default User;
<|endoftext|>


<commit_before>/*
beer.js
Provides JSON data that represents a list of beers on tap, in firkins,
and in special bottles for sale at Al's of Hampden in Enola, PA

Copyright (c) 2014

Patrick Crager

*/

// required modules
var express = require('express'),
		cookieParser = require('cookie-parser'),
		request = require('request'),
		cheerio = require('cheerio'),
		console = require('clim')('beer'),
		mongojs = require('mongojs'),
		utils = require('./utils'),
		responses = require('./responses'),
		converter = require('./converter');

// request options
var listOptions = {
			url: 'http://alsofhampden.com/beer.php'
		},
		loginOptions = {
			url: 'https://untappd.com/oauth/authorize',
			json: true,
			qs: {
				client_id: process.env.untappdClientID,
				client_secret: process.env.untappdClientSecret,
				response_type: 'code',
				redirect_url: 'http://mccrager.com/nodejs/beer/login',
				code: null
			}
		},
		anonSearchOptions = {
			url: 'https://api.untappd.com/v4/search/beer',
			json: true,
			qs: {
				client_id: process.env.untappdClientID,
				client_secret: process.env.untappdClientSecret,
				limit: 1
			}
		},
		authSearchOptions = {
			url: 'https://api.untappd.com/v4/search/beer',
			json: true,
			qs: {
				access_token: null,
				limit: 1
			}
		};

function untappdSearch(res, beer, token) {
	var result = {},
	    requestOptions,
	    db = mongojs('beer', ['collection']);

	// set access_token if token found in cookie to do authenticated search
	// otherwise use client id/secret for anon search
	if (token) {
		requestOptions = authSearchOptions;
		requestOptions.qs.access_token = token;
	} else {
		requestOptions = anonSearchOptions;
	}

	// if not found, fetch from untappd, store in db, and write out the response
	requestOptions.qs.q = encodeURIComponent(beer.brewery + ' ' + beer.name);

	// fire off request to untappd search api
	request(requestOptions, function (error, response, json) {
		if (!error && response.statusCode === 200) {
			if (json.response.found > 0 && json.response.beers.items.length > 0) {
				result = json.response.beers.items[0].beer;
				result.brewery = json.response.beers.items[0].brewery;
				result.checkin_count = json.response.beers.items[0].checkin_count;
				result.have_had = json.response.beers.items[0].have_had;
				result.your_count = json.response.beers.items[0].your_count;

				var beerRecord = {
					brewery: beer.brewery,
					name: beer.name,
					data: result,
					created: new Date(),
					fetchMethod: 'untappd'
				};

				if (token) {
					responses.sendSuccess(res, beerRecord, true);
				} else {
					// persist to db
					db.collection.save(beerRecord, function(err, doc) {
						// handle exception from save
						if (err) {
							responses.sendError(res, {
								desc: responses.exceptions.databaseError,
								code: 500,
								error: err
							});
							return;
						}

						responses.sendSuccess(res, doc, true);
						// log the persisted beer
						console.log('new beer persisted: ' + doc.brewery + ' ' + doc.name);
					});
				}
			} else {
				responses.sendError(res, {
					desc: responses.exceptions.untappdSearchError,
					code: 404,
					query: requestOptions.qs.q
				});
			}
		} else {
			responses.sendError(res, {
				desc: responses.exceptions.untappdError,
				code: 500,
				response: response,
				error: error
			});
		}

		// unset the access_token
		authSearchOptions.qs.access_token = '';
	});
}

function databaseSearch(res, beer) {
	var result = {},
	    db = mongojs('beer', ['collection']);

	// look up beer in db
	db.collection.findOne({
		brewery: beer.brewery,
		name: beer.name
	}, function(err, doc) {
		// handle exception from query
		if (err) {
			responses.sendError(res, {
				desc: responses.exceptions.databaseError,
				code: 500,
				error: err
			});
			return;
		}

		// if we got a result from the db, write out the record
		// otherwise do an anon untappd search
		if (doc) {
			result = doc;
			result.fetchMethod = 'mongodb';
			responses.sendSuccess(res, result, true);
		} else {
			untappdSearch(res, beer);
		}
	});
}

// app bootstrap
var app = express();
app.use(cookieParser());

/*
 * Process OAuth login callback from untappd.
 * Action: GET
 * Params: code, the unique code value to be sent to untappd to validate the OAuth communication.
 */
app.get('/nodejs/beer/login', function (req, res) {

	// handle callback and get unique code from untappd
	var code = req.query.code;

	// if we didn't get a code bail out
	if (!code) {
		responses.sendError(res, {
			desc: responses.exceptions.untappdLoginError,
			code: 500,
			request: request
		});
		return;
	}

	// add the code to the login options
	loginOptions.qs.code = code;

	// fire off request to untappd login api with unique code
	request(loginOptions, function (error, response, json) {
		if (!error && response.statusCode === 200 &&
			   json.meta.http_code === 200 && json.response.access_token) {
			// get response and set json cookie with token
			res.cookie('untappdToken', json.response.access_token);
			// redirect to main page
			responses.sendRedirect(res, {
				location: '/beer/alstest'
			});
		} else {
			responses.sendError(res, {
				desc: responses.exceptions.untappdError,
				code: 500,
				response: response,
				error: error
			});
		}

		// unset the unique code
		loginOptions.qs.code = '';
	});
});

/*
 * Process Logout by clearing the untappdToken cookie.
 * Action: GET
 * Params: none.
 */
app.get('/nodejs/beer/logout', function (req, res) {
	res.clearCookie('untappdToken');
	responses.sendRedirect(res, {
		location: '/beer/alstest'
	});
});

/*
 * Search for a beer by brewery + name.
 * Action: GET
 * Params: brewery, the name of the brewery
 *         name, the name of the beer
 */
app.get('/nodejs/beer/search', function (req, res) {
	if (req.query.brewery && req.query.name) {
		// sanitize the query string input
		var beer = {
			brewery: decodeURIComponent(req.query.brewery),
			name: decodeURIComponent(req.query.name)
		};
		// apply conversions to the beer input
		converter.handle(beer);
		beer.brewery = utils.trim(beer.brewery);
		beer.name = utils.trim(beer.name);

		// check for token in cookie
		// always do an untappd search if logged in
		// otherwise check local db cache before doing anon untappd search
		var token = req.cookies.untappdToken;
		if (token) {
			untappdSearch(res, beer, token);
		} else {
			databaseSearch(res, beer);
		}
	} else {
		responses.sendError(res, {
			desc: responses.exceptions.queryError,
			code: 500
		});
	}
});

/*
 * Get the full list of beers.
 * Action: GET
 * Params: none
 */
app.get('/nodejs/beer', function (req, res) {
	var results = [];

	request(listOptions, function (error, response, html) {
		if (!error && response.statusCode === 200) {
			var $ = cheerio.load(html);

			var titles = $('#feature .row h2');

			// loop over the tables that represent beer lists
			$('#feature .row table').each(function(tableIndex, beerTable) {

				var $beerTable = $(beerTable),
						// result object to be added to the results array
						result = {},
						// get the title for this beerTable
						title = $(titles[tableIndex]).text(),
						// array to add beers in this table to
						beers = [];

				// loop over the beer rows in this table
				$beerTable.find('tbody > tr').each(function(rowIndex, beerRow) {
					var $beerRow = $(beerRow),
							beer = utils.createBeer(rowIndex),
							cellCount = $beerRow.find('td').length,
							// hack to handle varying cell amounts for draft beer vs. all others
							cellIndexModifier = (cellCount === 4 ? 1 : 0);

					// loop over the beer cells in this row
					$beerRow.find('td').each(function(cellIndex, beerCell) {
						var $beerCell = $(beerCell),
								cellValue = utils.trim($beerCell.text());
								cellIndex = $beerCell.index() + cellIndexModifier;

						// populates the passed in beer object by mapping cell text to beer properties
						utils.mapBeerValues(cellIndex, cellValue, beer);
					});

					// add beer object to beers array
					beers.push(beer);
				});

				// set the title for this beerTable
				result.title = utils.trim(title);
				result.title = utils.toLower(title);
				// set the resulting beers array
				result.beers = beers;
				// capture the current time this list was generated
				result.timestamp = new Date();
				// push the finalized result in the overall results array
				results.push(result);
			});

			responses.sendSuccess(res, results.reverse(), true);
		} else {
			responses.sendError(res, {
				desc: responses.exceptions.alsError,
				code: 500,
				response: response,
				error: error
			});
		}
	});

	// log usage
	console.log(req.headers['x-forwarded-for'] + '\n' + req.headers['user-agent'] + '\n');
});

// app startup
app.listen(process.env.PORT);
<commit_msg>Moved into production!<commit_after>/*
beer.js
Provides JSON data that represents a list of beers on tap, in firkins,
and in special bottles for sale at Al's of Hampden in Enola, PA

Copyright (c) 2014

Patrick Crager

*/

// required modules
var express = require('express'),
		cookieParser = require('cookie-parser'),
		request = require('request'),
		cheerio = require('cheerio'),
		console = require('clim')('beer'),
		mongojs = require('mongojs'),
		utils = require('./utils'),
		responses = require('./responses'),
		converter = require('./converter');

// request options
var listOptions = {
			url: 'http://alsofhampden.com/beer.php'
		},
		loginOptions = {
			url: 'https://untappd.com/oauth/authorize',
			json: true,
			qs: {
				client_id: process.env.untappdClientID,
				client_secret: process.env.untappdClientSecret,
				response_type: 'code',
				redirect_url: 'http://mccrager.com/nodejs/beer/login',
				code: null
			}
		},
		anonSearchOptions = {
			url: 'https://api.untappd.com/v4/search/beer',
			json: true,
			qs: {
				client_id: process.env.untappdClientID,
				client_secret: process.env.untappdClientSecret,
				limit: 1
			}
		},
		authSearchOptions = {
			url: 'https://api.untappd.com/v4/search/beer',
			json: true,
			qs: {
				access_token: null,
				limit: 1
			}
		};

function untappdSearch(res, beer, token) {
	var result = {},
	    requestOptions,
	    db = mongojs('beer', ['collection']);

	// set access_token if token found in cookie to do authenticated search
	// otherwise use client id/secret for anon search
	if (token) {
		requestOptions = authSearchOptions;
		requestOptions.qs.access_token = token;
	} else {
		requestOptions = anonSearchOptions;
	}

	// if not found, fetch from untappd, store in db, and write out the response
	requestOptions.qs.q = encodeURIComponent(beer.brewery + ' ' + beer.name);

	// fire off request to untappd search api
	request(requestOptions, function (error, response, json) {
		if (!error && response.statusCode === 200) {
			if (json.response.found > 0 && json.response.beers.items.length > 0) {
				result = json.response.beers.items[0].beer;
				result.brewery = json.response.beers.items[0].brewery;
				result.checkin_count = json.response.beers.items[0].checkin_count;
				result.have_had = json.response.beers.items[0].have_had;
				result.your_count = json.response.beers.items[0].your_count;

				var beerRecord = {
					brewery: beer.brewery,
					name: beer.name,
					data: result,
					created: new Date(),
					fetchMethod: 'untappd'
				};

				if (token) {
					responses.sendSuccess(res, beerRecord, true);
				} else {
					// persist to db
					db.collection.save(beerRecord, function(err, doc) {
						// handle exception from save
						if (err) {
							responses.sendError(res, {
								desc: responses.exceptions.databaseError,
								code: 500,
								error: err
							});
							return;
						}

						responses.sendSuccess(res, doc, true);
						// log the persisted beer
						console.log('new beer persisted: ' + doc.brewery + ' ' + doc.name);
					});
				}
			} else {
				responses.sendError(res, {
					desc: responses.exceptions.untappdSearchError,
					code: 404,
					query: requestOptions.qs.q
				});
			}
		} else {
			responses.sendError(res, {
				desc: responses.exceptions.untappdError,
				code: 500,
				response: response,
				error: error
			});
		}

		// unset the access_token
		authSearchOptions.qs.access_token = '';
	});
}

function databaseSearch(res, beer) {
	var result = {},
	    db = mongojs('beer', ['collection']);

	// look up beer in db
	db.collection.findOne({
		brewery: beer.brewery,
		name: beer.name
	}, function(err, doc) {
		// handle exception from query
		if (err) {
			responses.sendError(res, {
				desc: responses.exceptions.databaseError,
				code: 500,
				error: err
			});
			return;
		}

		// if we got a result from the db, write out the record
		// otherwise do an anon untappd search
		if (doc) {
			result = doc;
			result.fetchMethod = 'mongodb';
			responses.sendSuccess(res, result, true);
		} else {
			untappdSearch(res, beer);
		}
	});
}

// app bootstrap
var app = express();
app.use(cookieParser());

/*
 * Process OAuth login callback from untappd.
 * Action: GET
 * Params: code, the unique code value to be sent to untappd to validate the OAuth communication.
 */
app.get('/nodejs/beer/login', function (req, res) {

	// handle callback and get unique code from untappd
	var code = req.query.code;

	// if we didn't get a code bail out
	if (!code) {
		responses.sendError(res, {
			desc: responses.exceptions.untappdLoginError,
			code: 500,
			request: request
		});
		return;
	}

	// add the code to the login options
	loginOptions.qs.code = code;

	// fire off request to untappd login api with unique code
	request(loginOptions, function (error, response, json) {
		if (!error && response.statusCode === 200 &&
			   json.meta.http_code === 200 && json.response.access_token) {
			// get response and set json cookie with token
			res.cookie('untappdToken', json.response.access_token);
			// redirect to main page
			responses.sendRedirect(res, {
				location: '/beer/als'
			});
		} else {
			responses.sendError(res, {
				desc: responses.exceptions.untappdError,
				code: 500,
				response: response,
				error: error
			});
		}

		// unset the unique code
		loginOptions.qs.code = '';
	});
});

/*
 * Process Logout by clearing the untappdToken cookie.
 * Action: GET
 * Params: none.
 */
app.get('/nodejs/beer/logout', function (req, res) {
	res.clearCookie('untappdToken');
	responses.sendRedirect(res, {
		location: '/beer/als'
	});
});

/*
 * Search for a beer by brewery + name.
 * Action: GET
 * Params: brewery, the name of the brewery
 *         name, the name of the beer
 */
app.get('/nodejs/beer/search', function (req, res) {
	if (req.query.brewery && req.query.name) {
		// sanitize the query string input
		var beer = {
			brewery: decodeURIComponent(req.query.brewery),
			name: decodeURIComponent(req.query.name)
		};
		// apply conversions to the beer input
		converter.handle(beer);
		beer.brewery = utils.trim(beer.brewery);
		beer.name = utils.trim(beer.name);

		// check for token in cookie
		// always do an untappd search if logged in
		// otherwise check local db cache before doing anon untappd search
		var token = req.cookies.untappdToken;
		if (token) {
			untappdSearch(res, beer, token);
		} else {
			databaseSearch(res, beer);
		}
	} else {
		responses.sendError(res, {
			desc: responses.exceptions.queryError,
			code: 500
		});
	}
});

/*
 * Get the full list of beers.
 * Action: GET
 * Params: none
 */
app.get('/nodejs/beer', function (req, res) {
	var results = [];

	request(listOptions, function (error, response, html) {
		if (!error && response.statusCode === 200) {
			var $ = cheerio.load(html);

			var titles = $('#feature .row h2');

			// loop over the tables that represent beer lists
			$('#feature .row table').each(function(tableIndex, beerTable) {

				var $beerTable = $(beerTable),
						// result object to be added to the results array
						result = {},
						// get the title for this beerTable
						title = $(titles[tableIndex]).text(),
						// array to add beers in this table to
						beers = [];

				// loop over the beer rows in this table
				$beerTable.find('tbody > tr').each(function(rowIndex, beerRow) {
					var $beerRow = $(beerRow),
							beer = utils.createBeer(rowIndex),
							cellCount = $beerRow.find('td').length,
							// hack to handle varying cell amounts for draft beer vs. all others
							cellIndexModifier = (cellCount === 4 ? 1 : 0);

					// loop over the beer cells in this row
					$beerRow.find('td').each(function(cellIndex, beerCell) {
						var $beerCell = $(beerCell),
								cellValue = utils.trim($beerCell.text());
								cellIndex = $beerCell.index() + cellIndexModifier;

						// populates the passed in beer object by mapping cell text to beer properties
						utils.mapBeerValues(cellIndex, cellValue, beer);
					});

					// add beer object to beers array
					beers.push(beer);
				});

				// set the title for this beerTable
				result.title = utils.trim(title);
				result.title = utils.toLower(title);
				// set the resulting beers array
				result.beers = beers;
				// capture the current time this list was generated
				result.timestamp = new Date();
				// push the finalized result in the overall results array
				results.push(result);
			});

			responses.sendSuccess(res, results.reverse(), true);
		} else {
			responses.sendError(res, {
				desc: responses.exceptions.alsError,
				code: 500,
				response: response,
				error: error
			});
		}
	});

	// log usage
	console.log(req.headers['x-forwarded-for'] + '\n' + req.headers['user-agent'] + '\n');
});

// app startup
app.listen(process.env.PORT);
<|endoftext|>