<commit_before>/*
 * Copyright (c) 2011 Joshua Paine, <http://letterblock.com/>
 * Available under the MIT or GPL v2 license. You do not need to reproduce
 * the full license text here.
 */
(function($){
  var origTrigger = $.fn.trigger;
  $.fn.trigger = function(type){
    origTrigger.apply(this,arguments);
    if(type.doneWithHandlers){ type.doneWithHandlers(); };
    return this;
  }

  var Pencil = function(type){
    $.Event.call(this,type);
    this._dfr = new $.Deferred();
    this._waitingFor = [true];
  }
  Pencil.prototype = new $.Event('');

  Pencil.prototype.waitFor = function(promise){
    this._waitingFor.push(promise);
  };
  Pencil.prototype.doneWithHandlers = function(){
    if(this.isDefaultPrevented()){
      this._dfr.reject();
    } else {
      $.when.apply($,this._waitingFor).then(this._dfr.resolve,this._dfr.reject);
    }
  };
  Pencil.prototype.then = function(){ return this._dfr.then.apply(this._dfr,arguments); };
  Pencil.prototype.done = function(){ return this._dfr.done.apply(this._dfr,arguments); };
  Pencil.prototype.fail = function(){ return this._dfr.fail.apply(this._dfr,arguments); };
  Pencil.prototype.isResolved = function(){ return this._dfr.isResolved.apply(this._dfr,arguments); };
  Pencil.prototype.isRejected = function(){ return this._dfr.isRejected.apply(this._dfr,arguments); };

  $.Pencil = Pencil;
})(jQuery);
<commit_msg>Make Pencil pseudo-promise also implement the promise() method<commit_after>/*
 * Copyright (c) 2011 Joshua Paine, <http://letterblock.com/>
 * Available under the MIT or GPL v2 license. You do not need to reproduce
 * the full license text here.
 */
(function($){
  var origTrigger = $.fn.trigger;
  $.fn.trigger = function(type){
    origTrigger.apply(this,arguments);
    if(type.doneWithHandlers){ type.doneWithHandlers(); };
    return this;
  }

  var Pencil = function(type){
    $.Event.call(this,type);
    this._dfr = new $.Deferred();
    this._waitingFor = [true];
  }
  Pencil.prototype = new $.Event('');

  Pencil.prototype.waitFor = function(promise){
    this._waitingFor.push(promise);
  };
  Pencil.prototype.doneWithHandlers = function(){
    if(this.isDefaultPrevented()){
      this._dfr.reject();
    } else {
      $.when.apply($,this._waitingFor).then(this._dfr.resolve,this._dfr.reject);
    }
  };
  Pencil.prototype.then = function(){ return this._dfr.then.apply(this._dfr,arguments); };
  Pencil.prototype.done = function(){ return this._dfr.done.apply(this._dfr,arguments); };
  Pencil.prototype.fail = function(){ return this._dfr.fail.apply(this._dfr,arguments); };
  Pencil.prototype.isResolved = function(){ return this._dfr.isResolved.apply(this._dfr,arguments); };
  Pencil.prototype.isRejected = function(){ return this._dfr.isRejected.apply(this._dfr,arguments); };
  Pencil.prototype.promise = function(){ return this._dfr.promise(); };

  $.Pencil = Pencil;
})(jQuery);
<|endoftext|>


<commit_before>'use strict';

var restify = require('restify');
var session = require('client-sessions');
var async = require('async');
var rarity = require('rarity');
var mongoose = require('mongoose');
var url = require('url');
var kue = require('kue');
var http = require('http');
var https = require('https');
var Anyfetch = require('anyfetch');
var lru = require('lru-cache');

var Token = require('./models/token.js');
var TempToken = require('./models/temp-token.js');

var connected = false;
var connectMongo = function(mongoUrl) {
  if(!connected) {
    // Connect mongoose
    mongoose.connect(mongoUrl || process.env.MONGO_URL || "mongodb://localhost/anyfetch-provider");
    connected = true;
  }
};

/**
* Simple wrapper around the token model.
* Only one token should match.
*
* retrieveData({accessToken: ...}) => data for this access token
* retrieveData({'data.grant': ...}) => data with this value of grant.
*/
module.exports.retrieveData = function(hash, cb) {
  Token.findOne(hash, function(err, token) {
    if(!token) {
      return cb(new Error("no data matches"));
    }
    cb(err, token.data);
  });
};

/**
 * Create a new provider server.
 * This server will use `config.task_generator` as its main function, to turn a file into metadata.
 *
 * @param {Object} config Configuration hash.
 *   Mandatory:
*       task_generator, to retrieve a list of tasks (a task is "a document to upload").
*       task_handler, the uploading function.
*    Optional:
*       concurrency, max number of simultaneous calls to your task_handler function (default is 1)
 */
module.exports.createServer = function createServer(connectFunctions, updateAccount, workers, config) {
  // Connect mongo
  connectMongo(config.mongoUrl);

  // Load endpoints generators
  var indexEndpoint = require('./handlers/index.js');
  var initConnectEndpoints = require('./handlers/init/connect.js');
  var initCallbackEndpoints = require('./handlers/init/callback.js');
  var updateEndpoint = require('./handlers/update.js');
  var statusEndpoint = require('./handlers/status.js');
  var resetEndpoint = require('./handlers/reset.js');

  http.globalAgent.maxSockets = config.maxSockets || 30;
  https.globalAgent.maxSockets = http.globalAgent.maxSockets;

  // Create server
  var server = restify.createServer();
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());
  server.use(session({
    cookieName: 'ANYFETCH_SESSION',
    secret: config.appSecret
  }));

  var components = url.parse(config.redisUrl || process.env.REDIS_URL || "redis://localhost");
  server.queue = kue.createQueue({
    prefix: config.providerUrl,
    redis: {
      port: components.port || 6379,
      host: components.hostname || "localhost",
      auth: (components.auth) ? ((components.auth.split(':').length > 1) ? components.auth.split(':')[1] : components.auth) : undefined,
    },
    disableSearch: true
  });

  // When jobs are completed, we can delete them
  server.queue.on('job complete', function(id) {
    kue.Job.get(id, function(err, job) {
      if(err) {
        return console.log("ERROR", err);
      }

      job.remove();
    });
  });

  function sigtermKue() {
    server.queue.shutdown(function(err) {
      console.log('Kue is shut down.', err || '');
      process.exit(0);
    }, 5000);
  }

  process.once('SIGTERM', sigtermKue);

  // One element have a length of '1', so we can stock 50 elements
  server.tokenCache = lru({
    max: 50,
    length: function () { return 1; }
  });

  server.userCache = lru({
    max: 50,
    length: function () { return 1; }
  });

  Object.keys(workers).forEach(function createQueue(name) {
    server.queue.process(name, (workers[name].concurrency) ? workers[name].concurrency : 1, function(job, done) {
      job.task = job.data;

      job.anyfetchClient = new Anyfetch(job.task._anyfetchToken);
      job.anyfetchClient.setApiUrl(job.task._anyfetchApiUrl);

      async.waterfall([
        function retrieveToken(cb) {
          if(server.tokenCache.has(job.task._anyfetchToken)) {
            cb(null, server.tokenCache.get(job.task._anyfetchToken));
          }
          else {
            Token.findOne({anyfetchToken: job.task._anyfetchToken}, rarity.slice(2, cb));
          }
        },
        function executeTask(token, cb) {
          if(!token) {
            return cb(new Error("Can't retrieve Token"));
          }

          console.log("User `" + token.accountName + "` is executing task " + job.id + " onto `" + name + "`");

          server.tokenCache.set(job.task._anyfetchToken, token);

          job.serviceData = token.data;
          job.cache = server.userCache;

          // We use task instead of data
          delete job.data;
          delete job.task._anyfetchToken;
          delete job.task._anyfetchApiUrl;

          workers[name](job, cb);
        }
      ], done);
    });
  });

  // Load routes and generate endpoints using closures
  server.get('/', indexEndpoint);
  server.get('/init/connect', initConnectEndpoints(connectFunctions.redirectToService, config));
  server.get('/init/callback', initCallbackEndpoints(connectFunctions.retrieveTokens, config));

  server.post('/update', updateEndpoint(updateAccount, server.queue, workers));

  server.get('/status', statusEndpoint);
  server.del('/reset', resetEndpoint);

  // Expose the server
  return server;
};

module.exports.debug = {
  cleanTokens: function(cb) {
    connectMongo();
    async.parallel([
      function(cb) {
        Token.remove({}, cb);
      },
      function(cb) {
        TempToken.remove({}, cb);
      }
    ], cb);
  },
  createToken: function(hash, cb) {
    connectMongo();

    var token = new Token(hash);
    token.save(cb);
  },
  createTempToken: function(hash, cb) {
    connectMongo();

    var tempToken = new TempToken(hash);
    tempToken.save(cb);
  }
};
<commit_msg>Log error in jobs<commit_after>'use strict';

var restify = require('restify');
var session = require('client-sessions');
var async = require('async');
var rarity = require('rarity');
var mongoose = require('mongoose');
var url = require('url');
var kue = require('kue');
var http = require('http');
var https = require('https');
var Anyfetch = require('anyfetch');
var lru = require('lru-cache');

var Token = require('./models/token.js');
var TempToken = require('./models/temp-token.js');

var connected = false;
var connectMongo = function(mongoUrl) {
  if(!connected) {
    // Connect mongoose
    mongoose.connect(mongoUrl || process.env.MONGO_URL || "mongodb://localhost/anyfetch-provider");
    connected = true;
  }
};

/**
* Simple wrapper around the token model.
* Only one token should match.
*
* retrieveData({accessToken: ...}) => data for this access token
* retrieveData({'data.grant': ...}) => data with this value of grant.
*/
module.exports.retrieveData = function(hash, cb) {
  Token.findOne(hash, function(err, token) {
    if(!token) {
      return cb(new Error("no data matches"));
    }
    cb(err, token.data);
  });
};

/**
 * Create a new provider server.
 * This server will use `config.task_generator` as its main function, to turn a file into metadata.
 *
 * @param {Object} config Configuration hash.
 *   Mandatory:
*       task_generator, to retrieve a list of tasks (a task is "a document to upload").
*       task_handler, the uploading function.
*    Optional:
*       concurrency, max number of simultaneous calls to your task_handler function (default is 1)
 */
module.exports.createServer = function createServer(connectFunctions, updateAccount, workers, config) {
  // Connect mongo
  connectMongo(config.mongoUrl);

  // Load endpoints generators
  var indexEndpoint = require('./handlers/index.js');
  var initConnectEndpoints = require('./handlers/init/connect.js');
  var initCallbackEndpoints = require('./handlers/init/callback.js');
  var updateEndpoint = require('./handlers/update.js');
  var statusEndpoint = require('./handlers/status.js');
  var resetEndpoint = require('./handlers/reset.js');

  http.globalAgent.maxSockets = config.maxSockets || 30;
  https.globalAgent.maxSockets = http.globalAgent.maxSockets;

  // Create server
  var server = restify.createServer();
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());
  server.use(session({
    cookieName: 'ANYFETCH_SESSION',
    secret: config.appSecret
  }));

  var components = url.parse(config.redisUrl || process.env.REDIS_URL || "redis://localhost");
  server.queue = kue.createQueue({
    prefix: config.providerUrl,
    redis: {
      port: components.port || 6379,
      host: components.hostname || "localhost",
      auth: (components.auth) ? ((components.auth.split(':').length > 1) ? components.auth.split(':')[1] : components.auth) : undefined,
    },
    disableSearch: true
  });

  // When jobs are completed, we can delete them
  server.queue.on('job complete', function(id) {
    kue.Job.get(id, function(err, job) {
      if(err) {
        return console.log("ERROR", err);
      }

      job.remove();
    });
  });

  function sigtermKue() {
    server.queue.shutdown(function(err) {
      console.log('Kue is shut down.', err || '');
      process.exit(0);
    }, 5000);
  }

  process.once('SIGTERM', sigtermKue);

  // One element have a length of '1', so we can stock 50 elements
  server.tokenCache = lru({
    max: 50,
    length: function () { return 1; }
  });

  server.userCache = lru({
    max: 50,
    length: function () { return 1; }
  });

  Object.keys(workers).forEach(function createQueue(name) {
    server.queue.process(name, (workers[name].concurrency) ? workers[name].concurrency : 1, function(job, done) {
      job.task = job.data;

      job.anyfetchClient = new Anyfetch(job.task._anyfetchToken);
      job.anyfetchClient.setApiUrl(job.task._anyfetchApiUrl);

      async.waterfall([
        function retrieveToken(cb) {
          if(server.tokenCache.has(job.task._anyfetchToken)) {
            cb(null, server.tokenCache.get(job.task._anyfetchToken));
          }
          else {
            Token.findOne({anyfetchToken: job.task._anyfetchToken}, rarity.slice(2, cb));
          }
        },
        function executeTask(token, cb) {
          if(!token) {
            return cb(new Error("Can't retrieve Token"));
          }

          console.log("User `" + token.accountName + "` is executing task " + job.id + " onto `" + name + "`");

          server.tokenCache.set(job.task._anyfetchToken, token);

          job.serviceData = token.data;
          job.cache = server.userCache;

          // We use task instead of data
          delete job.data;
          delete job.task._anyfetchToken;
          delete job.task._anyfetchApiUrl;

          workers[name](job, cb);
        }
      ], function(err) {
        if(err) {
          console.log("Job " + job.id + " onto `" + name + "` have failed");
          console.log("ERR:", err);
        }
        done(err);
      });
    });
  });

  // Load routes and generate endpoints using closures
  server.get('/', indexEndpoint);
  server.get('/init/connect', initConnectEndpoints(connectFunctions.redirectToService, config));
  server.get('/init/callback', initCallbackEndpoints(connectFunctions.retrieveTokens, config));

  server.post('/update', updateEndpoint(updateAccount, server.queue, workers));

  server.get('/status', statusEndpoint);
  server.del('/reset', resetEndpoint);

  // Expose the server
  return server;
};

module.exports.debug = {
  cleanTokens: function(cb) {
    connectMongo();
    async.parallel([
      function(cb) {
        Token.remove({}, cb);
      },
      function(cb) {
        TempToken.remove({}, cb);
      }
    ], cb);
  },
  createToken: function(hash, cb) {
    connectMongo();

    var token = new Token(hash);
    token.save(cb);
  },
  createTempToken: function(hash, cb) {
    connectMongo();

    var tempToken = new TempToken(hash);
    tempToken.save(cb);
  }
};
<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var BinWrapper = require('bin-wrapper');
var pkg = require('../package.json');
var url = 'https://raw.github.com/imagemin/gifsicle-bin/' + pkg.version + '/vendor/';

module.exports = new BinWrapper()
	.src(url + 'osx/gifsicle', 'darwin')
	.src(url + 'linux/x86/gifsicle', 'linux', 'x86')
	.src(url + 'linux/x64/gifsicle', 'linux', 'x64')
	.src(url + 'freebsd/x86/gifsicle', 'freebsd', 'x86')
	.src(url + 'freebsd/x64/gifsicle', 'freebsd', 'x64')
	.src(url + 'win/x86/gifsicle.exe', 'win32', 'x86')
	.src(url + 'win/x64/gifsicle.exe', 'win32', 'x64')
	.dest(path.join(__dirname, '../vendor'))
	.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
<commit_msg>Avoid redirect<commit_after>'use strict';

var path = require('path');
var BinWrapper = require('bin-wrapper');
var pkg = require('../package.json');
var url = 'https://raw.githubusercontent.com/imagemin/gifsicle-bin/' + pkg.version + '/vendor/';

module.exports = new BinWrapper()
	.src(url + 'osx/gifsicle', 'darwin')
	.src(url + 'linux/x86/gifsicle', 'linux', 'x86')
	.src(url + 'linux/x64/gifsicle', 'linux', 'x64')
	.src(url + 'freebsd/x86/gifsicle', 'freebsd', 'x86')
	.src(url + 'freebsd/x64/gifsicle', 'freebsd', 'x64')
	.src(url + 'win/x86/gifsicle.exe', 'win32', 'x86')
	.src(url + 'win/x64/gifsicle.exe', 'win32', 'x64')
	.dest(path.join(__dirname, '../vendor'))
	.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
<|endoftext|>


<commit_before>"use strict";

const readJson = require("r-json")
    , writeJson = require("w-json")
    , abs = require("abs")
    , path = require("path")
    , enginePaths = require("engine-paths")
    ;

class EngineApp {
    constructor (appPath) {
        this.paths = new EnginePaths(appPath);
    }
    getPackage (fn) {
        return readJson(this.paths.package, fn);
    }
    setPackage (data, fn) {
        return readJson(this.package_path, data, fn);
    }
    readInstance (name, fn) {
        return readJson(path.join(
    }
}
<commit_msg>JSDoc comments<commit_after>"use strict";

const readJson = require("r-json")
    , writeJson = require("w-json")
    , abs = require("abs")
    , path = require("path")
    , enginePaths = require("engine-paths")
    , CompositionCrud = require("engine-composition-crud")
    ;

class EngineApp {
    /**
     * EngineApp
     * Creates a new instance of `EngineApp`.
     *
     * @name EngineApp
     * @function
     * @param {String} appPath The app path.
     */
    constructor (appPath, compositionCrud) {
        this.paths = new EnginePaths(appPath);
        this.crud = compositionCrud || new CompositionCrud(this.paths);
    }

    /**
     * getPackage
     * Gets the package.json content.
     *
     * @name getPackage
     * @function
     * @param {Function} fn The callback function.
     * @return {Object} The `package.json` content as json (if called without a callback function).
     */
    getPackage (fn) {
        return readJson(this.paths.package, fn);
    }

    /**
     * setPackage
     * Sets the package.json content.
     *
     * @name setPackage
     * @function
     * @param {Object} data The data to set in the file.
     * @param {Function} fn The callback function.
     */
    setPackage (data, fn) {
        return writeJson(this.paths.package, data, fn);
    }

    /**
     * readInstance
     * Gets the instance content.
     *
     * @name readInstance
     * @function
     * @param {String} name The instance name.
     * @param {Function} fn The callback function.
     * @return {Object} The instance content as json (if called without a callback function).
     */
    readInstance (name, fn) {
        return readJson(this.paths.instance(name), fn);
    }
}
<|endoftext|>


<commit_before>var express = require('express');
var url = require('url');
var net = require('net');
var path = require('path');
var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
var exnted = require('util')._extend;
var util = require('../util');
var rules = require('./rules');
var rulesUtil = require('./rules/util');
var connect = require('./proxy/connect');
var config = require('../package.json');

function tunnelProxy(server, proxy) {

	server.on('connect', function(req, reqSocket, head) {//ws, wss, https proxy
		var tunnelUrl = req.url = util.setProtocol(req.url, true);
		var options = parseUrl();
		var resSocket, proxySocket;

		if (!options.hostname) {
			tunnelUrl = req.url = util.setProtocol(req.headers.host, true);
			parseUrl();
		}
		abortIfUnavailable(reqSocket);
		function emitError(err) {
			resSocket && resSocket.destroy();
			proxySocket && proxySocket.destroy();
			reqSocket.destroy();
		}

		function abortIfUnavailable(socket) {
			socket.on('error', emitError).on('close', emitError);
		}

		function parseUrl(_tunnelUrl, port) {
			_tunnelUrl = _tunnelUrl || tunnelUrl;
			options = req.options = url.parse(_tunnelUrl);
			options.port = options.port || port || 443;
			return options;
		}

		function sendEstablished() {
			reqSocket.write('HTTP/1.1 200 Connection Established\r\nProxy-Agent: ' + config.name + '\r\n\r\n');
			return reqSocket;
		}

		function tunnel() {
			rules.resolveHost(tunnelUrl, function(err, ip) {
				req.host = ip;
				proxy.emit('tunnel', req);

				if (err) {
					emitError(err);
					return;
				}

				resSocket = net.connect(options.port, ip || options.hostname, function() {
					sendEstablished();
		            resSocket.pipe(reqSocket).pipe(resSocket);
		        });
				abortIfUnavailable(resSocket);
			});
		}

		var _rules = rules.resolveRules(removeDefaultPort(tunnelUrl));
		var proxyUrl = util.rule.getMatcher(_rules.proxy);
		if (proxyUrl) {
			var _tunnelUrl = 'https:' + util.removeProtocol(proxyUrl);
			rules.resolveHost(_tunnelUrl, function(err, ip) {
				req.host = ip;
				parseUrl(_tunnelUrl, config.port);
				if (!err && util.isLocalAddress(ip) && options.port == config.port) {
					parseUrl();
					return tunnel();
				}
				proxy.emit('tunnelProxy', req);
				if (err) {
					emitError(err);
					return;
				}
				options.host = ip;
				connect(req, config, function(err, _proxySocket) {
					if (err) {
						return emitError();
					}
					proxySocket = _proxySocket;
					abortIfUnavailable(_proxySocket);
					sendEstablished().pipe(_proxySocket).pipe(reqSocket);
				});
			});
		} else {
			tunnel();
		}
    });

	return server;
}

function removeDefaultPort(url) {
	var index = url.lastIndexOf(':');
	if (index != -1 && url.substring(index + 1) == '443') {
		url = url.substring(0, index);
	}

	return url;
}

function proxy(conf) {
	if (conf) {
		['port', 'sockets', 'days', 'timeout',
		 'username', 'password', 'uipath', 'rules']
			.forEach(function(name) {
				if (conf[name]) {
					config[name] = conf[name];
				}
			});

		if (Array.isArray(conf.ports)) {
			config.ports = config.ports.concat(conf.ports);
		}
	}

	if (!conf || !Array.isArray(conf.middlewares)) {
		config.middlewares = [];
	} else if (typeof conf.middlewares == 'string') {
		config.middlewares = conf.middlewares.trim().split(/\s*,\s*/g);
	}

	var port = config.port;
	config.ports.forEach(function(name) {
		config[name] = ++port;
	});

	var agentConfig = {maxSockets: config.sockets};
	config.httpAgent = new (require('http').Agent)(agentConfig);
	config.httpsAgent = new (require('https').Agent)(agentConfig);
	config.WEINRE_HOST = 'weinre.' + config.localUIHost;

	var app = express();
	var server = app.listen(config.port);
	var proxyEvents = new EventEmitter();
	var middlewares = ['./init',
	               '../biz']
				   .concat(require('./inspectors'))
				   .concat(config.middlewares)
	               .concat(require('./handlers'));

	proxyEvents.config = config;
	middlewares.forEach(function(mw) {
		mw && app.use((typeof mw == 'string' ? require(mw) : mw).bind(proxyEvents));
	});

	parseHosts(config.rules);
	exportInterfaces(proxyEvents);
	tunnelProxy(server, proxyEvents);
	require('./proxy')(proxyEvents);
	require('../biz/init')(proxyEvents);

	return proxyEvents;
}

function exportInterfaces(obj) {
	obj.rules = rules;
	obj.util = util;
	obj.rulesUtil = rulesUtil;
	return obj;
}

function parseHosts(rulesPath) {
	if (rulesPath) {
		try {
			rulesUtil.setPublicHosts(fs.readFileSync(path.resolve(rulesPath), {encoding: 'utf8'}));
			rulesUtil.enablePublicHosts();
		} catch(e) {}
	}

	rulesUtil.loadHosts();
}

process.on('uncaughtException', function(err){

	fs.writeFileSync(path.join(process.cwd(), config.name + '-log.log'), '\r\n' + util.formatDate()  + '\r\n' +  err.stack + '\r\n', {flag: 'a'});
	console.error(err.stack);
	process.exit(1);
});

module.exports = exportInterfaces(proxy);<commit_msg>add middlewares<commit_after>var express = require('express');
var url = require('url');
var net = require('net');
var path = require('path');
var fs = require('fs');
var EventEmitter = require('events').EventEmitter;
var exnted = require('util')._extend;
var util = require('../util');
var rules = require('./rules');
var rulesUtil = require('./rules/util');
var connect = require('./proxy/connect');
var config = require('../package.json');

function tunnelProxy(server, proxy) {

	server.on('connect', function(req, reqSocket, head) {//ws, wss, https proxy
		var tunnelUrl = req.url = util.setProtocol(req.url, true);
		var options = parseUrl();
		var resSocket, proxySocket;

		if (!options.hostname) {
			tunnelUrl = req.url = util.setProtocol(req.headers.host, true);
			parseUrl();
		}
		abortIfUnavailable(reqSocket);
		function emitError(err) {
			resSocket && resSocket.destroy();
			proxySocket && proxySocket.destroy();
			reqSocket.destroy();
		}

		function abortIfUnavailable(socket) {
			socket.on('error', emitError).on('close', emitError);
		}

		function parseUrl(_tunnelUrl, port) {
			_tunnelUrl = _tunnelUrl || tunnelUrl;
			options = req.options = url.parse(_tunnelUrl);
			options.port = options.port || port || 443;
			return options;
		}

		function sendEstablished() {
			reqSocket.write('HTTP/1.1 200 Connection Established\r\nProxy-Agent: ' + config.name + '\r\n\r\n');
			return reqSocket;
		}

		function tunnel() {
			rules.resolveHost(tunnelUrl, function(err, ip) {
				req.host = ip;
				proxy.emit('tunnel', req);

				if (err) {
					emitError(err);
					return;
				}

				resSocket = net.connect(options.port, ip || options.hostname, function() {
					sendEstablished();
		            resSocket.pipe(reqSocket).pipe(resSocket);
		        });
				abortIfUnavailable(resSocket);
			});
		}

		var _rules = rules.resolveRules(removeDefaultPort(tunnelUrl));
		var proxyUrl = util.rule.getMatcher(_rules.proxy);
		if (proxyUrl) {
			var _tunnelUrl = 'https:' + util.removeProtocol(proxyUrl);
			rules.resolveHost(_tunnelUrl, function(err, ip) {
				req.host = ip;
				parseUrl(_tunnelUrl, config.port);
				if (!err && util.isLocalAddress(ip) && options.port == config.port) {
					parseUrl();
					return tunnel();
				}
				proxy.emit('tunnelProxy', req);
				if (err) {
					emitError(err);
					return;
				}
				options.host = ip;
				connect(req, config, function(err, _proxySocket) {
					if (err) {
						return emitError();
					}
					proxySocket = _proxySocket;
					abortIfUnavailable(_proxySocket);
					sendEstablished().pipe(_proxySocket).pipe(reqSocket);
				});
			});
		} else {
			tunnel();
		}
    });

	return server;
}

function removeDefaultPort(url) {
	var index = url.lastIndexOf(':');
	if (index != -1 && url.substring(index + 1) == '443') {
		url = url.substring(0, index);
	}

	return url;
}

function proxy(conf) {
	if (conf) {
		['port', 'sockets', 'days', 'timeout',
		 'username', 'password', 'uipath', 'rules']
			.forEach(function(name) {
				if (conf[name]) {
					config[name] = conf[name];
				}
			});

		if (Array.isArray(conf.ports)) {
			config.ports = config.ports.concat(conf.ports);
		}
	}

	if (!conf || typeof conf.middlewares != 'string') {
		if (!Array.isArray(conf.middlewares)) {
			config.middlewares = [];
		}
	} else {
		config.middlewares = conf.middlewares.trim().split(/\s*,\s*/g);
	}

	var port = config.port;
	config.ports.forEach(function(name) {
		config[name] = ++port;
	});

	var agentConfig = {maxSockets: config.sockets};
	config.httpAgent = new (require('http').Agent)(agentConfig);
	config.httpsAgent = new (require('https').Agent)(agentConfig);
	config.WEINRE_HOST = 'weinre.' + config.localUIHost;

	var app = express();
	var server = app.listen(config.port);
	var proxyEvents = new EventEmitter();
	var middlewares = ['./init',
	               '../biz']
				   .concat(require('./inspectors'))
				   .concat(config.middlewares)
	               .concat(require('./handlers'));

	proxyEvents.config = config;
	middlewares.forEach(function(mw) {
		mw && app.use((typeof mw == 'string' ? require(mw) : mw).bind(proxyEvents));
	});

	parseHosts(config.rules);
	exportInterfaces(proxyEvents);
	tunnelProxy(server, proxyEvents);
	require('./proxy')(proxyEvents);
	require('../biz/init')(proxyEvents);

	return proxyEvents;
}

function exportInterfaces(obj) {
	obj.rules = rules;
	obj.util = util;
	obj.rulesUtil = rulesUtil;
	return obj;
}

function parseHosts(rulesPath) {
	if (rulesPath) {
		try {
			rulesUtil.setPublicHosts(fs.readFileSync(path.resolve(rulesPath), {encoding: 'utf8'}));
			rulesUtil.enablePublicHosts();
		} catch(e) {}
	}

	rulesUtil.loadHosts();
}

process.on('uncaughtException', function(err){

	fs.writeFileSync(path.join(process.cwd(), config.name + '-log.log'), '\r\n' + util.formatDate()  + '\r\n' +  err.stack + '\r\n', {flag: 'a'});
	console.error(err.stack);
	process.exit(1);
});

module.exports = exportInterfaces(proxy);<|endoftext|>


<commit_before>// Load modules

var Bell = require('bell');
var Blipp = require('blipp');
var Hapi = require('hapi');
var Hoek = require('hoek');
var HapiAuthCookie = require('hapi-auth-cookie');
var Api = require('./api');
var Authentication = require('./authentication');
var Controllers = require('./controllers');
var Models = require('./models');
var Routes = require('./routes');

// Internals

var internals = {
    servers: {
        http: {
            port: 8080,
            host: '0.0.0.0',
            labels: ['http']
        },
        api: {
            port: 8088,
            host: '0.0.0.0',
            labels: ['api']
        }
    },
    options: {
        files: {
            relativeTo: __dirname
        }
    }
};

var server;

exports.init = function (callback) {

    if (server) {
        return callback(null, server);
    }

    server = new Hapi.Server();
    server.connection(internals.servers.http);
    server.connection(internals.servers.api);
    server.path(internals.options.files.relativeTo);

    server.on('request-error', function (request, response) {

        console.log('request-error:');
        console.dir(response);
    });

    server.register([
            Bell,
            Blipp,
            HapiAuthCookie,
            Authentication,
            Controllers,
            Models,
            Routes
        ],
        { select: 'http' },
        function (err) {

            if (err) {
                console.log('server.register err:', err);
                return callback(err);
            }

            server.register([
                Blipp,
                Controllers,
                Models,
                Api
            ],
            { select: 'api' },
            function (err) {

                server.start(function() {
                    return callback(null, server);
                });

            });
        }
    );
};

exports.init(Hoek.ignore);
<commit_msg>use async for clarity<commit_after>// Load modules

var Async = require('async');
var Bell = require('bell');
var Blipp = require('blipp');
var Hapi = require('hapi');
var Hoek = require('hoek');
var HapiAuthCookie = require('hapi-auth-cookie');
var Api = require('./api');
var Authentication = require('./authentication');
var Controllers = require('./controllers');
var Models = require('./models');
var Routes = require('./routes');

// Internals

var internals = {
    servers: {
        http: {
            port: 8080,
            host: '0.0.0.0',
            labels: ['http']
        },
        api: {
            port: 8088,
            host: '0.0.0.0',
            labels: ['api']
        }
    },
    options: {
        files: {
            relativeTo: __dirname
        }
    }
};

var server;

exports.init = function (callback) {

    if (server) {
        return callback(null, server);
    }

    server = new Hapi.Server();
    server.connection(internals.servers.http);
    server.connection(internals.servers.api);
    server.path(internals.options.files.relativeTo);

    server.on('request-error', function (request, response) {

        console.log('request-error:');
        console.dir(response);
    });

    var registerHttpPlugins = function (next) {
        server.register([
                Bell,
                Blipp,
                HapiAuthCookie,
                Authentication,
                Controllers,
                Models,
                Routes
            ],
            { select: 'http' },
        function (err) {

            return next(err);
        });
    }

    var registerApiPlugins = function (next) {
        server.register([
                Blipp,
                Controllers,
                Models,
                Api
            ],
            { select: 'api' },
        function (err) {

            return next(err);
        });
    }

    Async.auto({
        http: registerHttpPlugins,
        api: registerApiPlugins
    }, function (err, data) {

        if (err) {
            console.log('server.register err:', err);
            return callback(err);
        }

        server.start(function() {
            return callback(null, server);
        });

    });
};

exports.init(Hoek.ignore);
<|endoftext|>


<commit_before>// some pseudo-constants used in the application:

// the app is distributed among some pages that can be addressed by this IDs
var pages = {
    START: "pStart",
    HOME: "pHome",
    DUPLICATES: "pDuplicates",
    CHANGECONTACT: "pChangeContact",
    FUNNYCHARS: "pFunnyCharacters",
    MISSINGPLUS: "pMissingPlus",
    ABOUT: "pAbout",
    DEBUG: "pDebug",
    RESTOREBACKUP: "pRestoreBackup"
};

// some elements in the pages have fixed IDs that can be found here
var ids = {
    CONTACTLIST: "CONTACTLIST",
    MISSINGPREFIXLIST: "MISSINGPREFIXLIST",
    CONTACTCHANGE: "CONTACTCHANGE",
    TEXTAREA: "TEXTAREA",
    CONTACTSREAD: "CONTACTSREAD",
    CBINTRONOTAGAIN: "CBINTRONOTAGAIN",
    MISSINGPREFIXPANEL: "MISSINGPREFIXPANEL",
    MISSINGPREFIXCONTENT: "MISSINGPREFIXCONTENT",
    INPUTPREFIX: "INPUTPREFIX",
    SELECTPREFIX: "SELECTPREFIX",
    NUMDUPLICATES: "NUMDUPLICATES",
    NUMMISSINGPREFIX: "NUMMISSINGPREFIX",
    NUMFUNNYCHAR: "NUMFUNNYCHAR",
    NUMMESSAGES: "NUMMESSAGES",
    RESTOREBACKUPPANEL: "RESTOREBACKUPPANEL",
    RESTOREBACKUPCONTENT: "RESTOREBACKUPCONTENT",
    BUTTONRESTOREBACKUP: "BUTTONRESTOREBACKUP",
    BUTTONRESTOREBACKUPLOSE: "RESTOREBACKUPCLOSE"
};

// error messages are not only written to the console, but to an text-area on the debug page
// so that we can trace problems even on an actual device.
// TODO: Add a Send Debug as email to developer function
function log(e) {
    console.log(e);
    $('#' + ids.TEXTAREA).append(e + "\n");
}

// all contacts are kept in this object during application runtime
var contactList = new cContactList();

/**
 * This function is usefull for debugging
 * TODO: Remove from the final product
 */
function getEventsList($obj) {
    var ev = [],
        events = jQuery._data($obj, "events"),
        i;
    for (i in events) {
        ev.push(i);
    }
    return ev.join(' ');
}

/**
 * This function is called once when the user interface is initialized
 */
function initApp() {
    // make the title a bit wider, so that more information
    // can be displayed
    $('.ui-title').css({
        'margin-left': '10%',
        'margin-right': '10%'
    });

    // link pages with their buttons.
    // each button knows its destination page by the data-nav attribute
    var buttons = {};
    buttons[pages.DUPLICATES] = $('[data-nav="nav.pDuplicates"]');
    buttons[pages.DEBUG] = $('[data-nav="nav.pDebug"]');
    buttons[pages.HOME] = $('[data-nav="nav.home"]');
    buttons[pages.FUNNYCHARS] = $('[data-nav="nav.pFunnyCharacters"]');
    buttons[pages.MISSINGPLUS] = $('[data-nav="nav.pMissingPlus"]');
    buttons[pages.ABOUT] = $('[data-nav="nav.pAbout"]');
    $.each(buttons, function(key, value) {
        value.click(function(e) {
            $(':mobile-pagecontainer').pagecontainer("change", '#' + key);
        });
    });
    // if the start button is called, an extra function is called: loadContacts
    // to init the contactList from the contacts stored in the device.
    buttons[pages.HOME].click(function(e) {
        loadContacts();
    });
    // some functions for the buttons on other pages
    try {
        $('[data-i18n = "duplicates.mergeall"]').click(function(e) {
            mergeAll();
        });
        $('[data-i18n = "missingplus.correctall"]').click(function(e) {
            correctAll();
        });
    } catch (ex) {
        log(ex);
    }

    //    $('#' + ids.CBINTRONOTAGAIN).on("click", function(e) {
    //        try {
    //            e.preventDefault();
    //            var $checkbox = $(this).find(':checkbox');
    //            $checkbox.trigger("checked");
    //        } catch (ex) {
    //            log(ex);
    //        }
    //    });

    //Init i18n and replace text on objects in the class="i18n"
    i18n.init(function(t) {
        // translate app
        $(".i18n").i18n();
    });
    try {
        // check if the intro was disabled by the user, and proceed to the start page
        var intronotagain = window.localStorage.getItem("ContactFox." + ids.CBINTRONOTAGAIN);
        log("intronotagain was selected: " + intronotagain);
        if (intronotagain == 'true') {
            loadContacts();
            $(':mobile-pagecontainer').pagecontainer("change", '#' + pages.HOME);
        }
    } catch (ex) {
        log(ex);
    }
    // The intro explains the purpose of the app and ask the user to confirm
    // the access to the contact list.
    // By a checkbox the user can control if he wants to see the intro again
    // on program start.
    // It is initialized to not show the intro again. This setting is stored in local
    // storage so it is there on program start
    var cb = $('#' + ids.CBINTRONOTAGAIN).find(':checkbox');
    cb.prop('checked', true).checkboxradio("refresh");
    window.localStorage.setItem("ContactFox." + ids.CBINTRONOTAGAIN, true);
    cb.bind('change', function(e) {
        var wasChecked = $(this).prop('checked');
        log("wasChecked " + wasChecked);
        window.localStorage.setItem("ContactFox." + ids.CBINTRONOTAGAIN, wasChecked);
    });
    // For debugging log all events that are bound to the checkbox and look if the state changed
    //log("EventList: " + getEventsList(cb[0]));
    //cb.on(getEventsList(cb[0]), function(e) {
    //    try {
    //        log("checkbox event: " + e.type);
    //        var wasChecked = $(this).prop('checked');
    //        log("Checkbox was checked " + wasChecked);
    //    } catch (ex) {
    //        log(ex);
    //    }
    //});
    $('#' + ids.SELECTPREFIX).bind('change', function(e) {
        var value = $('#' + ids.SELECTPREFIX).val();
        log("selectedPrefix: " + value);
        $('#' + ids.INPUTPREFIX).val(value);
    });
    $('#' + ids.INPUTPREFIX).bind('change', function(e) {
        var prefix = $("#" + ids.INPUTPREFIX).val();
        $('#' + ids.SELECTPREFIX + ' option').filter(function() {
            return $(this).text() == prefix;
        }).prop('selected', true);
        $('#' + ids.SELECTPREFIX).selectmenu('refresh', true);
    });
}

/**
 * depending on the content of the contact some functions are available or not.
 * If there is nothing to do the button for this function is disabled
 * This is done in this function
 */
function updateButtons() {
    if (contactList.hasDuplicates()) {
        $('[data-nav="nav.pDuplicates"]').removeClass('ui-disabled');
    } else {
        $('[data-nav="nav.pDuplicates"]').addClass('ui-disabled');
    }
    var nd = contactList.numDuplicates();
    $('#' + ids.NUMDUPLICATES).html(nd);
    if (contactList.hasFunnyCharacters()) {
        $('[data-nav="nav.pFunnyCharacters"]').removeClass('ui-disabled');
    } else {
        $('[data-nav="nav.pFunnyCharacters"]').addClass('ui-disabled');
    }
    $('#' + ids.NUMFUNNYCHARACTERS).html(contactList.numFunnyCharacters());
    if (contactList.hasMissingPrefix()) {
        $('[data-nav="nav.pMissingPlus"]').removeClass('ui-disabled');
    } else {
        $('[data-nav="nav.pMissingPlus"]').addClass('ui-disabled');
    }
    $('#' + ids.NUMMISSINGPREFIX).html(contactList.numMissingPrefix());
    // if the list changed, also replace the html lists on the pages
    replaceMissingPrefixListHTML();
}

/**
 * Read the contacts from the firefox OS device using its api.
 */
function loadContacts() {
    try {
        updateButtons();
        if (contactList.size() === 0) {
            contactList.addChangeListener(updateButtons);
            var allContacts = navigator.mozContacts.getAll({
                sortBy: "familyName",
                sortOrder: "descending"
            });

            allContacts.onsuccess = function(event) {
                try {
                    var cursor = event.target;
                    if (cursor.result) {
                        var contact = new cContact(cursor.result);
                        contactList.add(contact);
                        try {
                            $('#' + ids.CONTACTSREAD).text(" " + contactList.size());
                            // updateButtons();
                        } catch (ex) {
                            log(ex);
                        }
                        cursor.
                        continue();
                    } else {
                        addListsToHTML();
                    };
                } catch (ex) {
                    log(ex);
                };
            };

            allContacts.onerror = function() {
                //TODO: better error handling and i18n
                alert("Can not read your contacts. Did you give permissions?");
                log("Something went terribly wrong! :(");
            };
        };
    } catch (exception) {
        log(exception);
    };
}

/**
 * when all lists for the problems are available or they are changes, they are entered into the
 * application document
 */
function addListsToHTML() {
    replaceDuplicatesListHTML();
    replaceMissingPrefixListHTML();
}

/**
 * create the HTML-Code for the duplicates list and replace it at the prepared ID in the DOM
 */
function replaceDuplicatesListHTML() {
    // duplicates
    // let jquery mobile make this list filterable
    var cls = '#' + ids.CONTACTLIST;
    $(cls).empty();
    $(cls).append('

');
    contactList.appendUnifyListToUL($(cls + ' ul'));
    $(cls + ' ul li').click(function(event) {
        mergeContactSelected(event);
    });
    $(cls + " ul").listview()
        .listview('refresh');
}

/**
 * create the HTML-Code for the missing prefix list and replace it at the prepared ID in the DOM
 */
function replaceMissingPrefixListHTML() {
    //missingprefix
    // let jquery mobile make this list filterable
    var mls = '#' + ids.MISSINGPREFIXLIST;
    $(mls).empty();
    $(mls).append('

');
    contactList.appendMissingPrefixListToUL($(mls + ' ul'));
    $(mls + ' ul li').click(function(event) {
        addPrefixContactSelected(event);
    });
    $(mls + " ul").listview()
        .listview('refresh');
}

/**
 * this merges all contacts in one entry of the duplicates list.
 * It is also hidden from the list view after that.
 */
function merge(id) {
    $('#' + id).css("display", "none");
    contactList.merge(id);
}

/**
 * merge all entries in the duplicates list
 */
function mergeAll() {
    try {
        var cls = '#' + ids.CONTACTLIST;
        $(cls + ' ul li').each(function(i, e) {
            if (!$(e).hasClass('ui-screen-hidden')) {
                var id = $(e).attr("id");
                merge(id);
            }
        });
    } catch (ex) {
        log(ex);
    }
}

/**
 * This is called when an entry in the duplicates list is selected. A submenu is opened showing
 * the potential result of the merge. Buttons are created to apply or cancel the merge
 */
function mergeContactSelected(event) {
    try {
        var li = $(event.target).parent();
        var id = li.attr("id");
        var data = li.data("list");
        if (data.length > 0) {
            var contact = contactUtils.unifyContactList(data);
            var cname = contact.displayName();
            // TODO use a template
            var n = $('#' + ids.CONTACTCHANGE);
            n.empty();
            var mergeButtonID = 'merge' + contact.key();
            n.append('');
            n.append('

');
            n.append('
: ' + cname + '
');
            contact.appendAsString(n);
            n.append('
: ' + (data.length - 1) + '
');
            $('.contactcontent').css('font-weight', 'bold');
            $('.contactaction').css('color', 'blue');
            $('.contactaction').css('font-style', 'italic');
            $('#' + mergeButtonID).data("contact", contact);
            $('#' + mergeButtonID).data("data", data);
            $('#' + mergeButtonID).click(function(e) {
                try {
                    var contact = $(this).data("contact");
                    merge(contact.key());
                    $(':mobile-pagecontainer').pagecontainer("change", '#' + pages.DUPLICATES);
                } catch (ex) {
                    log(ex);
                }
            });

            // translate all
            $('#' + ids.CONTACTCHANGE).i18n();

            // apply styles to the newly created subelements of the page
            $('#' + pages.CHANGECONTACT).trigger("create");
            $(':mobile-pagecontainer').pagecontainer("change", '#' + pages.CHANGECONTACT);
        }
    } catch (exception) {
        log(exception);
    }
}

/**
 * correct the prefix in all phonenumbers of the given contact 'c' with the given 'prefix'
 * The contact is than hidden from the missing prefix list
 */
function correctPrefix(c, prefix) {
    log("correctPrefix " + c.key() + " " + prefix);
    contactList.correctPrefix(c.key(), prefix);
    $('#prefix' + c.key()).hide();
}

/**
 * correct all contacts in the missing prefix list
 */
function correctAll() {
    try {
        var prefix = $("#" + ids.INPUTPREFIX).val();
        var mpls = '#' + ids.MISSINGPREFIXLIST;
        $(mpls + ' ul li').each(function(i, e) {
            if (!$(e).hasClass('ui-screen-hidden')) {
                var id = $(e).attr("id");
                id = id.substring("prefix".length);
                c = contactList.getById(id);
                correctPrefix(c, prefix);
            }
        });
    } catch (ex) {
        log(ex);
    }
}

/**
 * This is called when an item in the missing prefix list is selected.
 * it opens a submenu, letting the user decide if he wants to correct this entry.
 */
function addPrefixContactSelected(event) {
    try {
        var prefix = $("#" + ids.INPUTPREFIX).val();
        var li = $(event.target).parent();
        var id = li.attr("id");
        var c = contactList.getById(id.substring('prefix'.length));
        var panel = $('#' + ids.MISSINGPREFIXPANEL);
        var content = $('#' + ids.MISSINGPREFIXCONTENT);
        content.empty();
        c.appendAsString(content);
        panel.i18n();
        panel.trigger("updatelayout");
        $('#close').click(function(e) {
            panel.panel("close");
        });
        $('#correct').click(function(e) {
            try {
                log("correct clicked");
                panel.panel("close");
                correctPrefix(c, prefix);
            } catch (ex) {
                log(ex);
            }
        });
        panel.panel("open");
    } catch (exception) {
        log(exception);
    }
}

// Init the start page
$(document)
    .on(
        'pagecreate',
        '#' + pages.START,
        function() {
            try {
                initApp();
            } catch (e) {
                log(e);
            }
        });

$(document)
    .on(
        'pagecreate',
        '#' + pages.DEBUG,
        function() {
            try {
                $('[data-i18n = "debug.addtestdata"]').click(function(e) {
                    try {
                        contactUtils.createDuplicateContactForTesting(contactList);
                    } catch (ex) {
                        log(ex);
                    }
                });
                $('[data-i18n = "debug.showintroagain"]').click(function(e) {
                    try {
                        log("showintroagain clicked");
                        var cb = $('#' + ids.CBINTRONOTAGAIN).find(':checkbox');
                        cb.prop('checked', false).checkboxradio("refresh");
                        cb.trigger("change");
                        var intronotagain = window.localStorage.getItem("ContactFox." + ids.CBINTRONOTAGAIN);
                        log("intronotagain was selected: " + intronotagain);
                    } catch (ex) {
                        log(ex);
                    }
                });
                //TODO: Add click function for backup restore
            } catch (ex) {
                log(ex);
            }
        });
<commit_msg>Some more comments on reading contacts from the adressbook<commit_after>// some pseudo-constants used in the application:

// the app is distributed among some pages that can be addressed by this IDs
var pages = {
    START: "pStart",
    HOME: "pHome",
    DUPLICATES: "pDuplicates",
    CHANGECONTACT: "pChangeContact",
    FUNNYCHARS: "pFunnyCharacters",
    MISSINGPLUS: "pMissingPlus",
    ABOUT: "pAbout",
    DEBUG: "pDebug",
    RESTOREBACKUP: "pRestoreBackup"
};

// some elements in the pages have fixed IDs that can be found here
var ids = {
    CONTACTLIST: "CONTACTLIST",
    MISSINGPREFIXLIST: "MISSINGPREFIXLIST",
    CONTACTCHANGE: "CONTACTCHANGE",
    TEXTAREA: "TEXTAREA",
    CONTACTSREAD: "CONTACTSREAD",
    CBINTRONOTAGAIN: "CBINTRONOTAGAIN",
    MISSINGPREFIXPANEL: "MISSINGPREFIXPANEL",
    MISSINGPREFIXCONTENT: "MISSINGPREFIXCONTENT",
    INPUTPREFIX: "INPUTPREFIX",
    SELECTPREFIX: "SELECTPREFIX",
    NUMDUPLICATES: "NUMDUPLICATES",
    NUMMISSINGPREFIX: "NUMMISSINGPREFIX",
    NUMFUNNYCHAR: "NUMFUNNYCHAR",
    NUMMESSAGES: "NUMMESSAGES",
    RESTOREBACKUPPANEL: "RESTOREBACKUPPANEL",
    RESTOREBACKUPCONTENT: "RESTOREBACKUPCONTENT",
    BUTTONRESTOREBACKUP: "BUTTONRESTOREBACKUP",
    BUTTONRESTOREBACKUPLOSE: "RESTOREBACKUPCLOSE"
};

// error messages are not only written to the console, but to an text-area on the debug page
// so that we can trace problems even on an actual device.
// TODO: Add a Send Debug as email to developer function
function log(e) {
    console.log(e);
    $('#' + ids.TEXTAREA).append(e + "\n");
}

// all contacts are kept in this object during application runtime
var contactList = new cContactList();

/**
 * This function is usefull for debugging
 * TODO: Remove from the final product
 */
function getEventsList($obj) {
    var ev = [],
        events = jQuery._data($obj, "events"),
        i;
    for (i in events) {
        ev.push(i);
    }
    return ev.join(' ');
}

/**
 * This function is called once when the user interface is initialized
 */
function initApp() {
    // make the title a bit wider, so that more information
    // can be displayed
    $('.ui-title').css({
        'margin-left': '10%',
        'margin-right': '10%'
    });

    // link pages with their buttons.
    // each button knows its destination page by the data-nav attribute
    var buttons = {};
    buttons[pages.DUPLICATES] = $('[data-nav="nav.pDuplicates"]');
    buttons[pages.DEBUG] = $('[data-nav="nav.pDebug"]');
    buttons[pages.HOME] = $('[data-nav="nav.home"]');
    buttons[pages.FUNNYCHARS] = $('[data-nav="nav.pFunnyCharacters"]');
    buttons[pages.MISSINGPLUS] = $('[data-nav="nav.pMissingPlus"]');
    buttons[pages.ABOUT] = $('[data-nav="nav.pAbout"]');
    $.each(buttons, function(key, value) {
        value.click(function(e) {
            $(':mobile-pagecontainer').pagecontainer("change", '#' + key);
        });
    });
    // if the start button is called, an extra function is called: loadContacts
    // to init the contactList from the contacts stored in the device.
    buttons[pages.HOME].click(function(e) {
        loadContacts();
    });
    // some functions for the buttons on other pages
    try {
        $('[data-i18n = "duplicates.mergeall"]').click(function(e) {
            mergeAll();
        });
        $('[data-i18n = "missingplus.correctall"]').click(function(e) {
            correctAll();
        });
    } catch (ex) {
        log(ex);
    }

    //    $('#' + ids.CBINTRONOTAGAIN).on("click", function(e) {
    //        try {
    //            e.preventDefault();
    //            var $checkbox = $(this).find(':checkbox');
    //            $checkbox.trigger("checked");
    //        } catch (ex) {
    //            log(ex);
    //        }
    //    });

    //Init i18n and replace text on objects in the class="i18n"
    i18n.init(function(t) {
        // translate app
        $(".i18n").i18n();
    });
    try {
        // check if the intro was disabled by the user, and proceed to the start page
        var intronotagain = window.localStorage.getItem("ContactFox." + ids.CBINTRONOTAGAIN);
        log("intronotagain was selected: " + intronotagain);
        if (intronotagain == 'true') {
            loadContacts();
            $(':mobile-pagecontainer').pagecontainer("change", '#' + pages.HOME);
        }
    } catch (ex) {
        log(ex);
    }
    // The intro explains the purpose of the app and ask the user to confirm
    // the access to the contact list.
    // By a checkbox the user can control if he wants to see the intro again
    // on program start.
    // It is initialized to not show the intro again. This setting is stored in local
    // storage so it is there on program start
    var cb = $('#' + ids.CBINTRONOTAGAIN).find(':checkbox');
    cb.prop('checked', true).checkboxradio("refresh");
    window.localStorage.setItem("ContactFox." + ids.CBINTRONOTAGAIN, true);
    cb.bind('change', function(e) {
        var wasChecked = $(this).prop('checked');
        log("wasChecked " + wasChecked);
        window.localStorage.setItem("ContactFox." + ids.CBINTRONOTAGAIN, wasChecked);
    });
    // For debugging log all events that are bound to the checkbox and look if the state changed
    //log("EventList: " + getEventsList(cb[0]));
    //cb.on(getEventsList(cb[0]), function(e) {
    //    try {
    //        log("checkbox event: " + e.type);
    //        var wasChecked = $(this).prop('checked');
    //        log("Checkbox was checked " + wasChecked);
    //    } catch (ex) {
    //        log(ex);
    //    }
    //});
    $('#' + ids.SELECTPREFIX).bind('change', function(e) {
        var value = $('#' + ids.SELECTPREFIX).val();
        log("selectedPrefix: " + value);
        $('#' + ids.INPUTPREFIX).val(value);
    });
    $('#' + ids.INPUTPREFIX).bind('change', function(e) {
        var prefix = $("#" + ids.INPUTPREFIX).val();
        $('#' + ids.SELECTPREFIX + ' option').filter(function() {
            return $(this).text() == prefix;
        }).prop('selected', true);
        $('#' + ids.SELECTPREFIX).selectmenu('refresh', true);
    });
}

/**
 * depending on the content of the contact some functions are available or not.
 * If there is nothing to do the button for this function is disabled
 * This is done in this function
 */
function updateButtons() {
    if (contactList.hasDuplicates()) {
        $('[data-nav="nav.pDuplicates"]').removeClass('ui-disabled');
    } else {
        $('[data-nav="nav.pDuplicates"]').addClass('ui-disabled');
    }
    var nd = contactList.numDuplicates();
    $('#' + ids.NUMDUPLICATES).html(nd);
    if (contactList.hasFunnyCharacters()) {
        $('[data-nav="nav.pFunnyCharacters"]').removeClass('ui-disabled');
    } else {
        $('[data-nav="nav.pFunnyCharacters"]').addClass('ui-disabled');
    }
    $('#' + ids.NUMFUNNYCHARACTERS).html(contactList.numFunnyCharacters());
    if (contactList.hasMissingPrefix()) {
        $('[data-nav="nav.pMissingPlus"]').removeClass('ui-disabled');
    } else {
        $('[data-nav="nav.pMissingPlus"]').addClass('ui-disabled');
    }
    $('#' + ids.NUMMISSINGPREFIX).html(contactList.numMissingPrefix());
    // if the list changed, also replace the html lists on the pages
    replaceMissingPrefixListHTML();
}

/**
 * Read the contacts from the firefox OS device using its api.
 */
function loadContacts() {
    try {
        updateButtons();
        if (contactList.size() === 0) {
            contactList.addChangeListener(updateButtons);
            var allContacts = navigator.mozContacts.getAll({
                sortBy: "familyName",
                sortOrder: "descending"
            });

            allContacts.onsuccess = function(event) {
                try {
                    var cursor = event.target;
                    if (cursor.result) {
                        // the cursor contains a result: Another contact was found,
                        //so we add it to the contactList
                        var contact = new cContact(cursor.result);
                        contactList.add(contact);
                        try {
                            $('#' + ids.CONTACTSREAD).text(" " + contactList.size());
                            // updateButtons();
                        } catch (ex) {
                            log(ex);
                        }
                        // find the next contact. This is marked as an error because
                        // the FirefoxOS API uses a reserved word as a function name
                        cursor.
                        continue();
                    } else {
                        // no more contacts were found: the contactList is added to the
                        // user interface
                        addListsToHTML();
                    };
                } catch (ex) {
                    log(ex);
                };
            };

            allContacts.onerror = function() {
                //TODO: better error handling and i18n
                alert("Can not read your contacts. Did you give permissions?");
                log("Something went terribly wrong! :(");
            };
        };
    } catch (exception) {
        log(exception);
    };
}

/**
 * when all lists for the problems are available or they are changes, they are entered into the
 * application document
 */
function addListsToHTML() {
    replaceDuplicatesListHTML();
    replaceMissingPrefixListHTML();
}

/**
 * create the HTML-Code for the duplicates list and replace it at the prepared ID in the DOM
 */
function replaceDuplicatesListHTML() {
    // duplicates
    // let jquery mobile make this list filterable
    var cls = '#' + ids.CONTACTLIST;
    $(cls).empty();
    $(cls).append('

');
    contactList.appendUnifyListToUL($(cls + ' ul'));
    $(cls + ' ul li').click(function(event) {
        mergeContactSelected(event);
    });
    $(cls + " ul").listview()
        .listview('refresh');
}

/**
 * create the HTML-Code for the missing prefix list and replace it at the prepared ID in the DOM
 */
function replaceMissingPrefixListHTML() {
    //missingprefix
    // let jquery mobile make this list filterable
    var mls = '#' + ids.MISSINGPREFIXLIST;
    $(mls).empty();
    $(mls).append('

');
    contactList.appendMissingPrefixListToUL($(mls + ' ul'));
    $(mls + ' ul li').click(function(event) {
        addPrefixContactSelected(event);
    });
    $(mls + " ul").listview()
        .listview('refresh');
}

/**
 * this merges all contacts in one entry of the duplicates list.
 * It is also hidden from the list view after that.
 */
function merge(id) {
    $('#' + id).css("display", "none");
    contactList.merge(id);
}

/**
 * merge all entries in the duplicates list
 */
function mergeAll() {
    try {
        var cls = '#' + ids.CONTACTLIST;
        $(cls + ' ul li').each(function(i, e) {
            if (!$(e).hasClass('ui-screen-hidden')) {
                var id = $(e).attr("id");
                merge(id);
            }
        });
    } catch (ex) {
        log(ex);
    }
}

/**
 * This is called when an entry in the duplicates list is selected. A submenu is opened showing
 * the potential result of the merge. Buttons are created to apply or cancel the merge
 */
function mergeContactSelected(event) {
    try {
        var li = $(event.target).parent();
        var id = li.attr("id");
        var data = li.data("list");
        if (data.length > 0) {
            var contact = contactUtils.unifyContactList(data);
            var cname = contact.displayName();
            // TODO use a template
            var n = $('#' + ids.CONTACTCHANGE);
            n.empty();
            var mergeButtonID = 'merge' + contact.key();
            n.append('');
            n.append('

');
            n.append('
: ' + cname + '
');
            contact.appendAsString(n);
            n.append('
: ' + (data.length - 1) + '
');
            $('.contactcontent').css('font-weight', 'bold');
            $('.contactaction').css('color', 'blue');
            $('.contactaction').css('font-style', 'italic');
            $('#' + mergeButtonID).data("contact", contact);
            $('#' + mergeButtonID).data("data", data);
            $('#' + mergeButtonID).click(function(e) {
                try {
                    var contact = $(this).data("contact");
                    merge(contact.key());
                    $(':mobile-pagecontainer').pagecontainer("change", '#' + pages.DUPLICATES);
                } catch (ex) {
                    log(ex);
                }
            });

            // translate all
            $('#' + ids.CONTACTCHANGE).i18n();

            // apply styles to the newly created subelements of the page
            $('#' + pages.CHANGECONTACT).trigger("create");
            $(':mobile-pagecontainer').pagecontainer("change", '#' + pages.CHANGECONTACT);
        }
    } catch (exception) {
        log(exception);
    }
}

/**
 * correct the prefix in all phonenumbers of the given contact 'c' with the given 'prefix'
 * The contact is than hidden from the missing prefix list
 */
function correctPrefix(c, prefix) {
    log("correctPrefix " + c.key() + " " + prefix);
    contactList.correctPrefix(c.key(), prefix);
    $('#prefix' + c.key()).hide();
}

/**
 * correct all contacts in the missing prefix list
 */
function correctAll() {
    try {
        var prefix = $("#" + ids.INPUTPREFIX).val();
        var mpls = '#' + ids.MISSINGPREFIXLIST;
        $(mpls + ' ul li').each(function(i, e) {
            if (!$(e).hasClass('ui-screen-hidden')) {
                var id = $(e).attr("id");
                id = id.substring("prefix".length);
                c = contactList.getById(id);
                correctPrefix(c, prefix);
            }
        });
    } catch (ex) {
        log(ex);
    }
}

/**
 * This is called when an item in the missing prefix list is selected.
 * it opens a submenu, letting the user decide if he wants to correct this entry.
 */
function addPrefixContactSelected(event) {
    try {
        var prefix = $("#" + ids.INPUTPREFIX).val();
        var li = $(event.target).parent();
        var id = li.attr("id");
        var c = contactList.getById(id.substring('prefix'.length));
        var panel = $('#' + ids.MISSINGPREFIXPANEL);
        var content = $('#' + ids.MISSINGPREFIXCONTENT);
        content.empty();
        c.appendAsString(content);
        panel.i18n();
        panel.trigger("updatelayout");
        $('#close').click(function(e) {
            panel.panel("close");
        });
        $('#correct').click(function(e) {
            try {
                log("correct clicked");
                panel.panel("close");
                correctPrefix(c, prefix);
            } catch (ex) {
                log(ex);
            }
        });
        panel.panel("open");
    } catch (exception) {
        log(exception);
    }
}

// Init the start page
$(document)
    .on(
        'pagecreate',
        '#' + pages.START,
        function() {
            try {
                initApp();
            } catch (e) {
                log(e);
            }
        });

$(document)
    .on(
        'pagecreate',
        '#' + pages.DEBUG,
        function() {
            try {
                $('[data-i18n = "debug.addtestdata"]').click(function(e) {
                    try {
                        contactUtils.createDuplicateContactForTesting(contactList);
                    } catch (ex) {
                        log(ex);
                    }
                });
                $('[data-i18n = "debug.showintroagain"]').click(function(e) {
                    try {
                        log("showintroagain clicked");
                        var cb = $('#' + ids.CBINTRONOTAGAIN).find(':checkbox');
                        cb.prop('checked', false).checkboxradio("refresh");
                        cb.trigger("change");
                        var intronotagain = window.localStorage.getItem("ContactFox." + ids.CBINTRONOTAGAIN);
                        log("intronotagain was selected: " + intronotagain);
                    } catch (ex) {
                        log(ex);
                    }
                });
                //TODO: Add click function for backup restore
            } catch (ex) {
                log(ex);
            }
        });
<|endoftext|>


<commit_before>var _ = require("lodash");

var tokenUtils = require('./tokens');

var WORD_BOUNDARY_CHARS = '\t\r\n\u00A0 !\"#$%&()*+,\-.\\/:;<=>?@\[\\\]^_`{|}~';
var WORD_BOUNDARY_REGEX = new RegExp('[' + WORD_BOUNDARY_CHARS + ']');
var SPLIT_REGEX = new RegExp(
    '([^' + WORD_BOUNDARY_CHARS + ']+)');

function Tokenizer(opts) {
    if (!(this instanceof Tokenizer)) return (new Tokenizer(opts));

    this.opts = _.defaults(opts || {}, {
        cacheGet: function(key) { return null; },
        cacheSet: function(key, value) { }
    });

    _.bindAll(this);
}

// Tokenize a text using a transformative function
Tokenizer.prototype.split = function tokenizeSplit(fn, opts) {
    var that = this;

    opts = _.defaults(opts || {}, {
        cache: _.constant(null)
    });

    return function(text) {
        var prev = undefined;
        var cacheId, cacheValue;

        // if string, convert to one large token
        if (_.isString(text)) {
            text = [
                {
                    value: text,
                    index: 0,
                    offset: text.length
                }
            ];
        }

        cacheId = tokenUtils.tokensId(text, opts.cache());
        if (cacheId) {
            cacheValue = that.opts.cacheGet(cacheId);
            if (cacheValue) {
                return cacheValue;
            }
        }

        return _.chain(text)
            .map(function(token, i) {
                var next = text[i + 1];
                var tokens = fn(
                    // Current text value
                    token.value,

                    // Current complete token
                    _.clone(token),

                    // Previous token
                    prev? _.clone(prev) : null,

                    // Next token
                    next? _.clone(next) : null,

                    // Index in the tokens list
                    i,

                    // List of all tokens
                    text
                ) || [];

                // Normalize tokens and return
                tokens = tokenUtils.normalize(token, tokens);

                // Update reference to prev
                prev = token;

                return tokens;
            })
            .compact()
            .flatten()
            .tap(function(_tokens) {
                if (!cacheId) return;

                that.opts.cacheSet(cacheId, _tokens);
            })
            .value();
    };
};

// Debug a tokenizing flow
Tokenizer.prototype.debug = function tokenizeDebug(prefix) {
    return this.filter(function(text, tok) {
        console.log(prefix || '', '[' + tok.index + '-' + (tok.index + tok.offset) + ']', JSON.stringify(tok.value));
        return true;
    });
};

// Tokenize a text using a RegExp
Tokenizer.prototype.re = function tokenizeRe(re, opts) {
    opts = _.defaults(opts || {}, {
        split: false
    });

    return this.split(function(text) {
        var originalText = text;
        var tokens = [];
        var match;
        var start = 0;
        var lastIndex = 0;

        while (match = re.exec(text)) {
            // Index in the current text section
            var index = match.index;

            // Index in the original text
            var absoluteIndex = start + index;

            var value = match[0] || "";
            var offset = value.length;

            // If splitting, push missed text
            if (opts.split && start < absoluteIndex) {
                var beforeText = originalText.slice(start, absoluteIndex);
                tokens.push({
                    value: beforeText,
                    index: start,
                    offset: beforeText.length
                });
            }

            tokens.push({
                value: value,
                index: absoluteIndex,
                offset: offset,
                match: match
            });

            text = text.slice(index + offset);
            start = absoluteIndex + offset;
        }

        // If splitting, push left text
        if (opts.split && text) {
            tokens.push({
                value: text,
                index: start,
                offset: text.length
            });
        }

        return tokens;
    }, {
        cache: function() {
            return re.toString();
        }
    });
};

// Split and merge tokens
// Used to split as sentences even if sentences is separated in multiple tokens (ex: markup)
// if fn results contain a 'null', it will split in two tokens
Tokenizer.prototype.splitAndMerge = function tokenizeSplitAndMerge(fn, opts) {
    var that = this;

    opts = _.defaults(opts || {}, {
        mergeWith: ''
    });

    return function(tokens) {
        var result = [];
        var accu = [];

        function pushAccu() {
            if (accu.length == 0) return;

            // Merge accumulator into one token
            var tok = tokenUtils.merge(accu, opts.mergeWith);

            result.push(tok);
            accu = [];
        }

        that.split(function(word, token) {
            var toks = fn.apply(null, arguments);

            // Normalize tokens
            toks = tokenUtils.normalize(token, toks);

            // Accumulate tokens and push to final results
            _.each(toks, function(tok) {
                if (tok === null) {
                    console.log(toks);
                    pushAccu();
                } else {
                    accu.push(tok);
                }
            });
        })(tokens);

        // Push tokens left in accumulator
        pushAccu();

        return result;
    };
};

// Filter when tokenising
Tokenizer.prototype.filter = function tokenizeFilter(fn) {
    return this.split(function(text, tok) {
        if (fn.apply(null, arguments)) {
            return tok.value;
        }
        return undefined;
    });
};

// Filter by testing a regex
Tokenizer.prototype.test = function tokenizeTest(re) {
    return this.filter(function(text, tok) {
        return re.test(text);
    }, {
        cache: re.toString()
    });
};

// Process token by all arguments
Tokenizer.prototype.flow = function tokenizeFlow() {
    var fn = _.flow.apply(_, arguments);
    return this.split(fn);
};

// Group and process a token as a group
Tokenizer.prototype.serie = function tokenizeFlow() {
    var fn = _.flow.apply(_, arguments);
    return fn;
};

// Merge all tokens into one
Tokenizer.prototype.merge = _.partial(Tokenizer.prototype.splitAndMerge, _.identity);

Tokenizer.prototype.sections = _.partial(Tokenizer.prototype.re, /([^\n\.,;!?]+)/i, { split: false });
Tokenizer.prototype.words = _.partial(Tokenizer.prototype.re, SPLIT_REGEX);
Tokenizer.prototype.characters = _.partial(Tokenizer.prototype.re, /[^\s]/);

module.exports = Tokenizer;
<commit_msg>Remove log<commit_after>var _ = require("lodash");

var tokenUtils = require('./tokens');

var WORD_BOUNDARY_CHARS = '\t\r\n\u00A0 !\"#$%&()*+,\-.\\/:;<=>?@\[\\\]^_`{|}~';
var WORD_BOUNDARY_REGEX = new RegExp('[' + WORD_BOUNDARY_CHARS + ']');
var SPLIT_REGEX = new RegExp(
    '([^' + WORD_BOUNDARY_CHARS + ']+)');

function Tokenizer(opts) {
    if (!(this instanceof Tokenizer)) return (new Tokenizer(opts));

    this.opts = _.defaults(opts || {}, {
        cacheGet: function(key) { return null; },
        cacheSet: function(key, value) { }
    });

    _.bindAll(this);
}

// Tokenize a text using a transformative function
Tokenizer.prototype.split = function tokenizeSplit(fn, opts) {
    var that = this;

    opts = _.defaults(opts || {}, {
        cache: _.constant(null)
    });

    return function(text) {
        var prev = undefined;
        var cacheId, cacheValue;

        // if string, convert to one large token
        if (_.isString(text)) {
            text = [
                {
                    value: text,
                    index: 0,
                    offset: text.length
                }
            ];
        }

        cacheId = tokenUtils.tokensId(text, opts.cache());
        if (cacheId) {
            cacheValue = that.opts.cacheGet(cacheId);
            if (cacheValue) {
                return cacheValue;
            }
        }

        return _.chain(text)
            .map(function(token, i) {
                var next = text[i + 1];
                var tokens = fn(
                    // Current text value
                    token.value,

                    // Current complete token
                    _.clone(token),

                    // Previous token
                    prev? _.clone(prev) : null,

                    // Next token
                    next? _.clone(next) : null,

                    // Index in the tokens list
                    i,

                    // List of all tokens
                    text
                ) || [];

                // Normalize tokens and return
                tokens = tokenUtils.normalize(token, tokens);

                // Update reference to prev
                prev = token;

                return tokens;
            })
            .compact()
            .flatten()
            .tap(function(_tokens) {
                if (!cacheId) return;

                that.opts.cacheSet(cacheId, _tokens);
            })
            .value();
    };
};

// Debug a tokenizing flow
Tokenizer.prototype.debug = function tokenizeDebug(prefix) {
    return this.filter(function(text, tok) {
        console.log(prefix || '', '[' + tok.index + '-' + (tok.index + tok.offset) + ']', JSON.stringify(tok.value));
        return true;
    });
};

// Tokenize a text using a RegExp
Tokenizer.prototype.re = function tokenizeRe(re, opts) {
    opts = _.defaults(opts || {}, {
        split: false
    });

    return this.split(function(text) {
        var originalText = text;
        var tokens = [];
        var match;
        var start = 0;
        var lastIndex = 0;

        while (match = re.exec(text)) {
            // Index in the current text section
            var index = match.index;

            // Index in the original text
            var absoluteIndex = start + index;

            var value = match[0] || "";
            var offset = value.length;

            // If splitting, push missed text
            if (opts.split && start < absoluteIndex) {
                var beforeText = originalText.slice(start, absoluteIndex);
                tokens.push({
                    value: beforeText,
                    index: start,
                    offset: beforeText.length
                });
            }

            tokens.push({
                value: value,
                index: absoluteIndex,
                offset: offset,
                match: match
            });

            text = text.slice(index + offset);
            start = absoluteIndex + offset;
        }

        // If splitting, push left text
        if (opts.split && text) {
            tokens.push({
                value: text,
                index: start,
                offset: text.length
            });
        }

        return tokens;
    }, {
        cache: function() {
            return re.toString();
        }
    });
};

// Split and merge tokens
// Used to split as sentences even if sentences is separated in multiple tokens (ex: markup)
// if fn results contain a 'null', it will split in two tokens
Tokenizer.prototype.splitAndMerge = function tokenizeSplitAndMerge(fn, opts) {
    var that = this;

    opts = _.defaults(opts || {}, {
        mergeWith: ''
    });

    return function(tokens) {
        var result = [];
        var accu = [];

        function pushAccu() {
            if (accu.length == 0) return;

            // Merge accumulator into one token
            var tok = tokenUtils.merge(accu, opts.mergeWith);

            result.push(tok);
            accu = [];
        }

        that.split(function(word, token) {
            var toks = fn.apply(null, arguments);

            // Normalize tokens
            toks = tokenUtils.normalize(token, toks);

            // Accumulate tokens and push to final results
            _.each(toks, function(tok) {
                if (tok === null) {
                    pushAccu();
                } else {
                    accu.push(tok);
                }
            });
        })(tokens);

        // Push tokens left in accumulator
        pushAccu();

        return result;
    };
};

// Filter when tokenising
Tokenizer.prototype.filter = function tokenizeFilter(fn) {
    return this.split(function(text, tok) {
        if (fn.apply(null, arguments)) {
            return tok.value;
        }
        return undefined;
    });
};

// Filter by testing a regex
Tokenizer.prototype.test = function tokenizeTest(re) {
    return this.filter(function(text, tok) {
        return re.test(text);
    }, {
        cache: re.toString()
    });
};

// Process token by all arguments
Tokenizer.prototype.flow = function tokenizeFlow() {
    var fn = _.flow.apply(_, arguments);
    return this.split(fn);
};

// Group and process a token as a group
Tokenizer.prototype.serie = function tokenizeFlow() {
    var fn = _.flow.apply(_, arguments);
    return fn;
};

// Merge all tokens into one
Tokenizer.prototype.merge = _.partial(Tokenizer.prototype.splitAndMerge, _.identity);

Tokenizer.prototype.sections = _.partial(Tokenizer.prototype.re, /([^\n\.,;!?]+)/i, { split: false });
Tokenizer.prototype.words = _.partial(Tokenizer.prototype.re, SPLIT_REGEX);
Tokenizer.prototype.characters = _.partial(Tokenizer.prototype.re, /[^\s]/);

module.exports = Tokenizer;
<|endoftext|>


<commit_before>function ImageTrack(trackMeta, url, refSeq, browserParams) {
    Track.call(this, trackMeta.label, trackMeta.key,
               false, browserParams.changeCallback);
    this.refSeq = refSeq;
    this.tileToImage = {};
    this.zoomCache = {};
    this.baseUrl = (browserParams.baseUrl ? browserParams.baseUrl : "");
    this.load(this.baseUrl + url);

    this.imgErrorHandler = function(ev) {
        var img = ev.target || ev.srcElement;
        img.style.display = "none";
        dojo.stopEvent(ev);
    };
}

ImageTrack.prototype = new Track("");

ImageTrack.prototype.loadSuccess = function(o) {
    //tileWidth: width, in pixels, of the tiles
    this.tileWidth = o.tileWidth;
    //zoomLevels: array of {basesPerTile, scale, height, urlPrefix} hashes
    this.zoomLevels = o.zoomLevels;
    this.setLoaded();
};

ImageTrack.prototype.setViewInfo = function(heightUpdate, numBlocks,
                                            trackDiv, labelDiv,
                                            widthPct, widthPx, scale) {
    Track.prototype.setViewInfo.apply(this, [heightUpdate, numBlocks,
                                             trackDiv, labelDiv,
                                             widthPct, widthPx, scale]);
    this.setLabel(this.key);
};

ImageTrack.prototype.getZoom = function(scale) {
    var result = this.zoomCache[scale];
    if (result) return result;

    result = this.zoomLevels[0];
    var desiredBases = this.tileWidth / scale;
    for (i = 1; i < this.zoomLevels.length; i++) {
        if (Math.abs(this.zoomLevels[i].basesPerTile - desiredBases)
            < Math.abs(result.basesPerTile - desiredBases))
            result = this.zoomLevels[i];
    }

    this.zoomCache[scale] = result;
    return result;
};

ImageTrack.prototype.getImages = function(zoom, startBase, endBase) {
    //var startTile = ((startBase - this.refSeq.start) / zoom.basesPerTile) | 0;
    //var endTile = ((endBase - this.refSeq.start) / zoom.basesPerTile) | 0;
    var startTile = (startBase / zoom.basesPerTile) | 0;
    var endTile = (endBase / zoom.basesPerTile) | 0;
    startTile = Math.max(startTile, 0);
    var result = [];
    var im;
    for (var i = startTile; i <= endTile; i++) {
	im = this.tileToImage[i];
	if (!im) {
	    im = document.createElement("img");
            dojo.connect(im, "onerror", this.imgErrorHandler);
            //prepend this.baseUrl if zoom.urlPrefix is relative
            im.src = (zoom.urlPrefix.match(/^(([^/]+:)|\/)/) ? "" : this.baseUrl)
                     + zoom.urlPrefix + i + ".png";
            //TODO: need image coord systems that don't start at 0?
	    im.startBase = (i * zoom.basesPerTile); // + this.refSeq.start;
	    im.baseWidth = zoom.basesPerTile;
	    im.tileNum = i;
	    this.tileToImage[i] = im;
	}
	result.push(im);
    }
    return result;
};

ImageTrack.prototype.fillBlock = function(blockIndex, block,
                                          leftBlock, rightBlock,
                                          leftBase, rightBase,
                                          scale, stripeWidth,
                                          containerStart, containerEnd) {
    var zoom = this.getZoom(scale);
    var blockWidth = rightBase - leftBase;
    var images = this.getImages(zoom, leftBase, rightBase);
    var im;

    for (var i = 0; i < images.length; i++) {
	im = images[i];
	if (!(im.parentNode && im.parentNode.parentNode)) {
            im.style.position = "absolute";
            im.style.left = (100 * ((im.startBase - leftBase) / blockWidth)) + "%";
            im.style.width = (100 * (im.baseWidth / blockWidth)) + "%";
            im.style.top = "0px";
            im.style.height = zoom.height + "px";
            block.appendChild(im);
	}
    }

    this.heightUpdate(zoom.height, blockIndex);
};

ImageTrack.prototype.startZoom = function(destScale, destStart, destEnd) {
    if (this.empty) return;
    this.tileToImage = {};
    this.getImages(this.getZoom(destScale), destStart, destEnd);
};

ImageTrack.prototype.endZoom = function(destScale, destBlockBases) {
    Track.prototype.clear.apply(this);
};

ImageTrack.prototype.clear = function() {
    Track.prototype.clear.apply(this);
    this.tileToImage = {};
};

ImageTrack.prototype.transfer = function(sourceBlock, destBlock, scale,
                                         containerStart, containerEnd) {
    if (!(sourceBlock && destBlock)) return;

    var children = sourceBlock.childNodes;
    var destLeft = destBlock.startBase;
    var destRight = destBlock.endBase;
    var im;
    for (var i = 0; i < children.length; i++) {
	im = children[i];
	if ("startBase" in im) {
	    //if sourceBlock contains an image that overlaps destBlock,
	    if ((im.startBase < destRight)
		&& ((im.startBase + im.baseWidth) > destLeft)) {
		//move image from sourceBlock to destBlock
		im.style.left = (100 * ((im.startBase - destLeft) / (destRight - destLeft))) + "%";
		destBlock.appendChild(im);
	    } else {
		delete this.tileToImage[im.tileNum];
	    }
	}
    }
};

/*

Copyright (c) 2007-2009 The Evolutionary Software Foundation

Created by Mitchell Skinner <mitch_skinner@berkeley.edu>

This package and its accompanying libraries are free software; you can
redistribute it and/or modify it under the terms of the LGPL (either
version 2.1, or at your option, any later version) or the Artistic
License 2.0.  Refer to LICENSE for the full license text.

*/
<commit_msg>shrinksafe didn't like the hairy regex, apparently<commit_after>function ImageTrack(trackMeta, url, refSeq, browserParams) {
    Track.call(this, trackMeta.label, trackMeta.key,
               false, browserParams.changeCallback);
    this.refSeq = refSeq;
    this.tileToImage = {};
    this.zoomCache = {};
    this.baseUrl = (browserParams.baseUrl ? browserParams.baseUrl : "");
    this.load(this.baseUrl + url);

    this.imgErrorHandler = function(ev) {
        var img = ev.target || ev.srcElement;
        img.style.display = "none";
        dojo.stopEvent(ev);
    };
}

ImageTrack.prototype = new Track("");

ImageTrack.prototype.loadSuccess = function(o) {
    //tileWidth: width, in pixels, of the tiles
    this.tileWidth = o.tileWidth;
    //zoomLevels: array of {basesPerTile, scale, height, urlPrefix} hashes
    this.zoomLevels = o.zoomLevels;
    this.setLoaded();
};

ImageTrack.prototype.setViewInfo = function(heightUpdate, numBlocks,
                                            trackDiv, labelDiv,
                                            widthPct, widthPx, scale) {
    Track.prototype.setViewInfo.apply(this, [heightUpdate, numBlocks,
                                             trackDiv, labelDiv,
                                             widthPct, widthPx, scale]);
    this.setLabel(this.key);
};

ImageTrack.prototype.getZoom = function(scale) {
    var result = this.zoomCache[scale];
    if (result) return result;

    result = this.zoomLevels[0];
    var desiredBases = this.tileWidth / scale;
    for (i = 1; i < this.zoomLevels.length; i++) {
        if (Math.abs(this.zoomLevels[i].basesPerTile - desiredBases)
            < Math.abs(result.basesPerTile - desiredBases))
            result = this.zoomLevels[i];
    }

    this.zoomCache[scale] = result;
    return result;
};

ImageTrack.prototype.getImages = function(zoom, startBase, endBase) {
    //var startTile = ((startBase - this.refSeq.start) / zoom.basesPerTile) | 0;
    //var endTile = ((endBase - this.refSeq.start) / zoom.basesPerTile) | 0;
    var startTile = (startBase / zoom.basesPerTile) | 0;
    var endTile = (endBase / zoom.basesPerTile) | 0;
    startTile = Math.max(startTile, 0);
    var result = [];
    var im;
    for (var i = startTile; i <= endTile; i++) {
	im = this.tileToImage[i];
	if (!im) {
	    im = document.createElement("img");
            dojo.connect(im, "onerror", this.imgErrorHandler);
            //prepend this.baseUrl if zoom.urlPrefix is relative
            var absUrl = new RegExp("^(([^/]+:)|\/)");
            im.src = (zoom.urlPrefix.match(absUrl) ? "" : this.baseUrl)
                     + zoom.urlPrefix + i + ".png";
            //TODO: need image coord systems that don't start at 0?
	    im.startBase = (i * zoom.basesPerTile); // + this.refSeq.start;
	    im.baseWidth = zoom.basesPerTile;
	    im.tileNum = i;
	    this.tileToImage[i] = im;
	}
	result.push(im);
    }
    return result;
};

ImageTrack.prototype.fillBlock = function(blockIndex, block,
                                          leftBlock, rightBlock,
                                          leftBase, rightBase,
                                          scale, stripeWidth,
                                          containerStart, containerEnd) {
    var zoom = this.getZoom(scale);
    var blockWidth = rightBase - leftBase;
    var images = this.getImages(zoom, leftBase, rightBase);
    var im;

    for (var i = 0; i < images.length; i++) {
	im = images[i];
	if (!(im.parentNode && im.parentNode.parentNode)) {
            im.style.position = "absolute";
            im.style.left = (100 * ((im.startBase - leftBase) / blockWidth)) + "%";
            im.style.width = (100 * (im.baseWidth / blockWidth)) + "%";
            im.style.top = "0px";
            im.style.height = zoom.height + "px";
            block.appendChild(im);
	}
    }

    this.heightUpdate(zoom.height, blockIndex);
};

ImageTrack.prototype.startZoom = function(destScale, destStart, destEnd) {
    if (this.empty) return;
    this.tileToImage = {};
    this.getImages(this.getZoom(destScale), destStart, destEnd);
};

ImageTrack.prototype.endZoom = function(destScale, destBlockBases) {
    Track.prototype.clear.apply(this);
};

ImageTrack.prototype.clear = function() {
    Track.prototype.clear.apply(this);
    this.tileToImage = {};
};

ImageTrack.prototype.transfer = function(sourceBlock, destBlock, scale,
                                         containerStart, containerEnd) {
    if (!(sourceBlock && destBlock)) return;

    var children = sourceBlock.childNodes;
    var destLeft = destBlock.startBase;
    var destRight = destBlock.endBase;
    var im;
    for (var i = 0; i < children.length; i++) {
	im = children[i];
	if ("startBase" in im) {
	    //if sourceBlock contains an image that overlaps destBlock,
	    if ((im.startBase < destRight)
		&& ((im.startBase + im.baseWidth) > destLeft)) {
		//move image from sourceBlock to destBlock
		im.style.left = (100 * ((im.startBase - destLeft) / (destRight - destLeft))) + "%";
		destBlock.appendChild(im);
	    } else {
		delete this.tileToImage[im.tileNum];
	    }
	}
    }
};

/*

Copyright (c) 2007-2009 The Evolutionary Software Foundation

Created by Mitchell Skinner <mitch_skinner@berkeley.edu>

This package and its accompanying libraries are free software; you can
redistribute it and/or modify it under the terms of the LGPL (either
version 2.1, or at your option, any later version) or the Artistic
License 2.0.  Refer to LICENSE for the full license text.

*/
<|endoftext|>


<commit_before>'use strict';
var esprima = require('esprima');
var fs = require('fs');
var utils = require('./utils.js');
var walker = require('./walker.js');
var util = require('util');

function run_esprima(code, options){
    var json = true;
    var bool = false;
    if (options){
        if (options.json !== undefined){
            json = options.json;
        }
        if (options.bool !== undefined){
            bool = options.bool;
        }
    }

    var parsed = esprima.parse(code, {
        loc: true,
        range: true,
    });
    var tree;
    var global_exits = [];
    var syntax = walker.syntax();

    var exit_funcs = utils.check_functions(parsed);

    for (var i = 0; i < parsed.body.length; i++){
        if (utils.is_exit(parsed.body[i], exit_funcs)){
            global_exits.push(parsed.body[i]);
        }
    }

    tree = walker.walk(parsed, code, syntax, exit_funcs);
    var extracted = extract(tree);
    var exits = extracted.exits;
    var non_exits = extracted.non_exits;
    var conditions = exits.concat(non_exits);

    for (var i = 0; i < exits.length; i++) {
        for (var j = 0; j < exits.length; j++) {
            if (i !== j){
                if (exits[j].condition.indexOf(exits[i].condition) === 0){
                    exits.splice(j, 1)
                }
            }
        }
        for (var j = 0; j < non_exits.length; j++){
            if (non_exits[j].condition.indexOf(exits[i].condition) === 0){
                non_exits.splice(j, 1);
            }
        }
    }

    for (var i = 0; i < global_exits.length; i++) {
        delete global_exits[i].type;
        delete global_exits[i].expression;
    }

    if (bool){
        if (global_exits.length){
            return true;
        } else{
            return false;
        }
    } else if (json){
        return {
            'exits': exits,
            'non_exits': non_exits,
            'global_exits': global_exits,
        }
    } else{
        if (global_exits.length){
            return "Hooray! Your code contains a global exit.";
        } else{
            if (non_exits.length){
                var str = "Your code does not contain an exit under the following conditions:\n\n";
                for (var i = 0; i < non_exits.length; i++) {
                    str += "  - " + non_exits[i].condition;
                    var start = non_exits[i].loc.start.line;
                    var end = non_exits[i].loc.end.line;
                    str += "\nlines " + start + "-" + end + "\n";
                }
                return str;
            } else{
                return "Hooray! Your code will always exit."
            }
        }
    }
}

function extract(tree){
    var str = '';
    var exits = [];
    var non_exits = [];

    for (var i = 0; i < tree.length; i++) {
        for (var j = 0; j < tree[i].exits.length; j++) {
            exits.push({
                'condition': tree[i].exits[j].condition,
                'loc': tree[i].exits[j].loc,
            });
        }
        for (var j = 0; j < tree[i].non_exits.length; j++) {
            non_exits.push({
                'condition': tree[i].non_exits[j].condition,
                'loc': tree[i].non_exits[j].loc,
            });
        }
    }

    return {
        'exits': exits,
        'non_exits': non_exits,
    }
}

function read_file(filename, options){
    if (filename !== undefined && filename !== null){
        var file = fs.readFileSync(filename, 'utf-8');
        return run_esprima(file, options);
    } else {
        throw new Error('No file name supplied.');
    }
}

module.exports = {
    'test_file': read_file,
    'test_string': run_esprima,
}<commit_msg>a teensy bit of output reformatting<commit_after>'use strict';
var esprima = require('esprima');
var fs = require('fs');
var utils = require('./utils.js');
var walker = require('./walker.js');
var util = require('util');

function run_esprima(code, options){
    var json = true;
    var bool = false;
    if (options){
        if (options.json !== undefined){
            json = options.json;
        }
        if (options.bool !== undefined){
            bool = options.bool;
        }
    }

    var parsed = esprima.parse(code, {
        loc: true,
        range: true,
    });
    var tree;
    var global_exits = [];
    var syntax = walker.syntax();

    var exit_funcs = utils.check_functions(parsed);

    for (var i = 0; i < parsed.body.length; i++){
        if (utils.is_exit(parsed.body[i], exit_funcs)){
            global_exits.push(parsed.body[i]);
        }
    }

    tree = walker.walk(parsed, code, syntax, exit_funcs);
    var extracted = extract(tree);
    var exits = extracted.exits;
    var non_exits = extracted.non_exits;
    var conditions = exits.concat(non_exits);

    for (var i = 0; i < exits.length; i++) {
        for (var j = 0; j < exits.length; j++) {
            if (i !== j){
                if (exits[j].condition.indexOf(exits[i].condition) === 0){
                    exits.splice(j, 1)
                }
            }
        }
        for (var j = 0; j < non_exits.length; j++){
            if (non_exits[j].condition.indexOf(exits[i].condition) === 0){
                non_exits.splice(j, 1);
            }
        }
    }

    for (var i = 0; i < global_exits.length; i++) {
        delete global_exits[i].type;
        delete global_exits[i].expression;
    }

    if (bool){
        if (global_exits.length){
            return true;
        } else{
            return false;
        }
    } else if (json){
        return {
            'exits': exits,
            'non_exits': non_exits,
            'global_exits': global_exits,
        }
    } else{
        if (global_exits.length){
            return "Hooray! Your code contains a global exit.";
        } else{
            if (non_exits.length){
                var str = "Your code does not contain an exit under the following conditions:\n\n";
                for (var i = 0; i < non_exits.length; i++) {
                    if (i !== 0){
                        str += "\n"
                    }
                    str += "  - " + non_exits[i].condition;
                    var start = non_exits[i].loc.start.line;
                    var end = non_exits[i].loc.end.line;
                    str += "\n    lines " + start + "-" + end;
                }
                return str;
            } else{
                return "Hooray! Your code will always exit."
            }
        }
    }
}

function extract(tree){
    var str = '';
    var exits = [];
    var non_exits = [];

    for (var i = 0; i < tree.length; i++) {
        for (var j = 0; j < tree[i].exits.length; j++) {
            exits.push({
                'condition': tree[i].exits[j].condition,
                'loc': tree[i].exits[j].loc,
            });
        }
        for (var j = 0; j < tree[i].non_exits.length; j++) {
            non_exits.push({
                'condition': tree[i].non_exits[j].condition,
                'loc': tree[i].non_exits[j].loc,
            });
        }
    }

    return {
        'exits': exits,
        'non_exits': non_exits,
    }
}

function read_file(filename, options){
    if (filename !== undefined && filename !== null){
        var file = fs.readFileSync(filename, 'utf-8');
        return run_esprima(file, options);
    } else {
        throw new Error('No file name supplied.');
    }
}

module.exports = {
    'test_file': read_file,
    'test_string': run_esprima,
}<|endoftext|>