<commit_before>(function (exports) {
	'use strict';
	/*jshint node:true */

	var request, fs, hub, scrape, writer, Writer, ROOT_URL, getKnownEntities, types, logger,
		uuid, knownEntities, Sync, SPARQLURL, Query, query, NOW, diseaseFiles, getDiseaseFiles;

	ROOT_URL = process.env.ROOT_URL;
	SPARQLURL = process.env.SPARQLURL;
	request = require('request');
	hub = require('node-pubsub');
	fs = require('fs');
	NOW = (new Date()).toISOString(); //add .slice(0,10) to get date only.

	// Generate a UUID
	uuid = function uuid(a){
		//Function from https://gist.github.com/982883 (@jed)
		return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid);
	};

	// Semaphore from https://gist.github.com/1296828
	Sync = function Sync(syncCount, callback, preventInstantCallbackExecution) {
		this.syncCount = syncCount;
		this.callback = callback;
		if(preventInstantCallbackExecution === false && this.syncCount === 0) {
			this.executeCallback();
		}
		Sync.prototype.decrement = function() {
			--this.syncCount;
			if(this.syncCount === 0) {
				this.executeCallback();
			}
		};
		Sync.prototype.increment = function() {
			++this.syncCount;
		};
		Sync.prototype.executeCallback = function() {
			if (typeof this.callback === "function") this.callback();
		};
	};

	// Writer Singleton
	Writer = (function (){
		var instantiated, init;

		init = function () {

			var writer = {}, fileName;

			fileName = "/tcgascrape"+Date.now()+".nt";

			writer.stream = fs.createWriteStream(process.cwd()+fileName);

			writer.listen = function (msg) {
				var triples = msg.triples;
				//triples = triples.split(" . ").join(" .\n");
				writer.stream.write(triples);
			};

			writer.close = function () {
				writer.stream.end();
			};

			return writer;

		};

		return {
			getInstance : function () {
				if (!instantiated) instantiated = init();
				return instantiated;
			}
		};
	})();

	// Query Singleton
	Query = (function (){
		var instantiated, init;

		init = function () {

			var query = {}, url, triples;

			url = process.env.SPARQLURL;
			triples = [];

			query.url = function (_) {
				if (!_) return url;
				else url = _;
				return query;
			};

			query.listen = function (msg) {
				triples.push(msg.triples);
				if (triples.length > 100) query.write();
			};

			query.write = function (){
				var insertQuery;
				insertQuery = 'INSERT DATA {\n'+triples.join("")+'\n}';
				triples = [];
				query.execute(insertQuery, function (error, response, body) {
					if (error || response.statusCode >= 300) {
						console.log("Unable to load triples:", insertQuery, error);
						console.log(response.statusCode, response.body);
						return;
					}
				});
			};

			query.flush = function () {
				query.write();
			};

			query.execute = function (query, callback) {
				request({
					url : url,//+encodeURIComponent(query),
					method : "POST",
					body : query
				}, callback);
			};

			return query;
		};

		return {
			getInstance : function () {
				if (!instantiated) instantiated = init();
				return instantiated;
			}
		};

	})();

	types = {
		9 : "DiseaseStudy",
		diseaseStudy : "DiseaseStudy",
		10 : "CenterType",
		11 : "CenterDomain",
		12 : "Platform",
		13 : "DataType",
		14 : "Archive",
		archive : "Archive",
		15 : "File",
		file : "File"
	};

	knownEntities = {};

	diseaseFiles = {};

	getKnownEntities = function (callback) {
		var query;

		query = [
			'prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>',
			'prefix tcga:<http://purl.org/tcga/core#>',
			'select distinct ?url ?id where {',
			'	?id tcga:url ?url .',
			'	{',
			'		?id a tcga:DiseaseStudy .',
			'	} UNION {',
			'		?id a tcga:CenterType .',
			'	} UNION {',
			'		?id a tcga:CenterDomain .',
			'	} UNION {',
			'		?id a tcga:Platform .',
			'	} UNION {',
			'		?id a tcga:DataType .',
			'	} UNION {',
			'		?id a tcga:Archive .',
			'	}',
			'}'
		].join(' ');

		request({
				method : "POST",
				url : SPARQLURL+encodeURIComponent(query),
				headers : { "Accept" : "application/sparql-results+json" }
			}, function ( error, response, body ) {

				if (response.statusCode === 204) {
					console.log('Found no known entities.');
					return callback();
				}

				if (error || response.statusCode !== 200) {
					console.log("Unable to query SPARQL endpoint");
					return;
				}

				var bindings;

				bindings = JSON.parse(body).results.bindings;

				bindings.forEach(function (binding) {
					knownEntities[binding.url.value] = binding;
				});

				console.log('Found', bindings.length, 'entities.');

				return callback();
		});
	};

	getDiseaseFiles = function (disease, callback) {
		var query;

		diseaseFiles = {};

		query = [
			'prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>',
			'prefix tcga:<http://purl.org/tcga/core#>',
			'select distinct ?url ?id where {',
			'	?id tcga:url ?url .',
			'	?id tcga:diseaseStudy ?d .',
			'	?d rdfs:label "'+disease+'" .',
			'}'
		].join(' ');

		request({
				method : "POST",
				url : SPARQLURL+encodeURIComponent(query),
				headers : { "Accept" : "application/sparql-results+json" }
			}, function ( error, response, body ) {

				if (response.statusCode === 204) {
					console.log("Found no entities for", disease, ".");
					return callback();
				}

				if (error || response.statusCode !== 200) {
					console.log("Unable to query SPARQL endpoint for",disease,"files.");
					return;
				}

				var bindings;

				bindings = JSON.parse(body).results.bindings;

				bindings.forEach(function (binding) {
					diseaseFiles[binding.url.value] = binding;
				});

				console.log('Found', bindings.length, 'entities for', disease,'.');

				return callback();
		});

	};

	scrape = function (options) {
		var opts, target, parent, callback, start, callCallback;

		opts = options || {};
		target = opts.target || ROOT_URL;
		parent = opts.parent || {};

		callCallback = function () {
			if (callback && typeof callback === 'function') callback();
		};
		callback = opts.callback;

		request(target, function (error, response, body) {

			if (error || response.statusCode !== 200) {
				console.log("Failed to get", target);
				return callCallback();
			}

			else {

				var pre, rows, children = [], tcga, literal;

				tcga = function (name) { return "<http://purl.org/tcga/core#"+name+">"; };
				literal = function (value) { return '"'+value+'"'; };
				pre = body.match(/<pre>([\s\S]*)<\/pre>/);
				if (!pre) {
					console.log("Could not parse (pre)", target);
					return callCallback();
				} else pre = pre[1];
				rows = pre.match(/<a[^>]+>[^<]+<\/a>\s+[\d-]{10}/g);

				if (rows) rows.forEach(function (row) {

					var name, id, url, type, tripleString, lastModified, subject, scrapeChildren = true, level;

					name = row.match(/>([\s\S]*)</);
					if (!name) console.log("Could not parse (name)", row); else name = name[1];
					id = uuid();
					url = target + row.match(/href="(.*)"/)[1];
					level = target.split('/').length;
					type = types[level];
					try {
						lastModified = (new Date(row.match(/\s([\d-]{10})/)[1])).toISOString().slice(0,10);
					} catch (e) {
						console.log("Row:", row);
						process.exit(1);
					}
					tripleString = "";

					if ((process.env.TESTING && level === 10) || level >= 15) {
						scrapeChildren = false;
						if (target.match(/ucec|thca|stad/)) scrapeChildren = true;
					}

					if (name === 'lost+found/') return;

					name = name.trim();

					// Things with extensions are files (e.g. reallylong_name.tiff)
					// TODO Use MIME types to determine files.
					if (name.match(/^.*\.[^\/]+$/)) {
						type = types.file;
						scrapeChildren = false;
					}

					// Remove the trailing "/"
					if (name.charAt(name.length-1) === "/") name = name.slice(0,-1);

					// If a file ends in .tar.gz or .tar.gz.md5, type it as an archive
					if (type === types.file && name.match(/\.tar\.gz($|\.md5$)/)) {
						type = types.archive;
					}

					// If a file is of the form
					// <domain>_<disease study>.<platform>.<archive type>.<serial index>.<revision>.<series>
					// type it as an archive
					if (name.match(/.*?_(.*?\.){5}\d$/)) {
						type = types.archive;
					}

					// Either insert or update, based on whether this item exists in the known entities.
					if (!knownEntities[url] && !diseaseFiles[url]) {
						var entity = {
							id : { value : id },
							newEntity : true
						};
						if (type !== types.file) knownEntities[url] = entity;
						else diseaseFiles[url] = entity;
						subject = id.length === 36 ? tcga(id) : "<" + id + ">";
						tripleString = [
							subject, tcga("lastSeen"), literal(NOW), ".\n",
							subject, tcga("firstSeen"), literal(NOW), ".\n",
							subject, "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>",  tcga(type), ".\n",
							subject, "<http://www.w3.org/2000/01/rdf-schema#label>", literal(name), ".\n",
							subject, tcga("url"), literal(url), ".\n"
						];

						if (type === types.file || types === types.archive) {
							tripleString.push(subject, tcga("lastModified"), literal(lastModified), ".\n");
						}

						if (type === types.file) {
							var ancestor;
							Object.keys(parent).forEach(function (ancestor) {
								// Make the first letter lowercase
								var property = ancestor.charAt(0).toLowerCase() + ancestor.slice(1);
								tripleString.push(subject, tcga(property), parent[ancestor], ".\n");
							});
						}
					}
					else {
						if (knownEntities[url]) id = knownEntities[url].id.value;
						else id = diseaseFiles[url].id.value;
						subject = id.length === 36 ? tcga(id) : "<" + id + ">";
						tripleString = [
							subject, tcga("lastSeen"), literal(NOW), ".\n"
						];

						// Additional URLS may need to be added for new entities, such as center types.
						if (knownEntities[url] && knownEntities[url].newEntity) {
							tripleString.push(subject, tcga("url"), literal(url), ".\n");
						}
					}

					hub.publish('/triples', [{
						triples : tripleString.join(" "),
						type : type,
						name : name
					}]);

					if (scrapeChildren){
						var child;
						parent[type] = subject;
						child = {target:url, parent:JSON.parse(JSON.stringify(parent))};
						if (type == types.diseaseStudy) {
							child.type = type;
							child.name = name;
						}
						children.push(child);
					}

				});

				if (children.length > 0) {
					var counter = new Sync(children.length, callback);
					var nextChild = function nextChild(children){
						var child = children.pop();
						if (child){
							child.callback = function(){
								counter.decrement();
								nextChild(children);
							};
							if (child.type === types.diseaseStudy) {
								getDiseaseFiles(child.name, function () {
									scrape(child);
								});
							} else {
								scrape(child);
							}
						}
					};
					nextChild(children);
				}
				else callCallback();

			}

		});
	};

	//writer = Writer.getInstance();

	query = Query.getInstance().url(process.env.SPARQLURL);

	hub.subscribe('/triples', query.listen);

	//hub.subscribe('/triples', writer.listen);

	console.log('Getting known entities from hub.');

	getKnownEntities(function (err) {
		if (err) console.log(err);
		console.log('Beginning scrape of', ROOT_URL);
		scrape({
			target : ROOT_URL,
			callback : function () {
				Query.getInstance().flush();
				// writer.close();
				console.log("Scrape finished");
			}
		});
	});

})( exports );
<commit_msg>Revert a63526a970a1fbf54e65898f6fd91cb212a3b7eb to reenable writing.<commit_after>(function (exports) {
	'use strict';
	/*jshint node:true */

	var request, fs, hub, scrape, writer, Writer, ROOT_URL, getKnownEntities, types, logger,
		uuid, knownEntities, Sync, SPARQLURL, Query, query, NOW, diseaseFiles, getDiseaseFiles;

	ROOT_URL = process.env.ROOT_URL;
	SPARQLURL = process.env.SPARQLURL;
	request = require('request');
	hub = require('node-pubsub');
	fs = require('fs');
	NOW = (new Date()).toISOString(); //add .slice(0,10) to get date only.

	// Generate a UUID
	uuid = function uuid(a){
		//Function from https://gist.github.com/982883 (@jed)
		return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid);
	};

	// Semaphore from https://gist.github.com/1296828
	Sync = function Sync(syncCount, callback, preventInstantCallbackExecution) {
		this.syncCount = syncCount;
		this.callback = callback;
		if(preventInstantCallbackExecution === false && this.syncCount === 0) {
			this.executeCallback();
		}
		Sync.prototype.decrement = function() {
			--this.syncCount;
			if(this.syncCount === 0) {
				this.executeCallback();
			}
		};
		Sync.prototype.increment = function() {
			++this.syncCount;
		};
		Sync.prototype.executeCallback = function() {
			if (typeof this.callback === "function") this.callback();
		};
	};

	// Writer Singleton
	Writer = (function (){
		var instantiated, init;

		init = function () {

			var writer = {}, fileName;

			fileName = "/tcgascrape"+Date.now()+".nt";

			writer.stream = fs.createWriteStream(process.cwd()+fileName);

			writer.listen = function (msg) {
				var triples = msg.triples;
				//triples = triples.split(" . ").join(" .\n");
				writer.stream.write(triples);
			};

			writer.close = function () {
				writer.stream.end();
			};

			return writer;

		};

		return {
			getInstance : function () {
				if (!instantiated) instantiated = init();
				return instantiated;
			}
		};
	})();

	// Query Singleton
	Query = (function (){
		var instantiated, init;

		init = function () {

			var query = {}, url, triples;

			url = process.env.SPARQLURL;
			triples = [];

			query.url = function (_) {
				if (!_) return url;
				else url = _;
				return query;
			};

			query.listen = function (msg) {
				triples.push(msg.triples);
				if (triples.length > 100) query.write();
			};

			query.write = function (){
				var insertQuery;
				insertQuery = 'INSERT DATA {\n'+triples.join("")+'\n}';
				triples = [];
				query.execute(insertQuery, function (error, response, body) {
					if (error || response.statusCode >= 300) {
						console.log("Unable to load triples:", insertQuery, error);
						console.log(response.statusCode, response.body);
						return;
					}
				});
			};

			query.flush = function () {
				query.write();
			};

			query.execute = function (query, callback) {
				request({
					url : url+encodeURIComponent(query),
					method : "POST"
				}, callback);
			};

			return query;
		};

		return {
			getInstance : function () {
				if (!instantiated) instantiated = init();
				return instantiated;
			}
		};

	})();

	types = {
		9 : "DiseaseStudy",
		diseaseStudy : "DiseaseStudy",
		10 : "CenterType",
		11 : "CenterDomain",
		12 : "Platform",
		13 : "DataType",
		14 : "Archive",
		archive : "Archive",
		15 : "File",
		file : "File"
	};

	knownEntities = {};

	diseaseFiles = {};

	getKnownEntities = function (callback) {
		var query;

		query = [
			'prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>',
			'prefix tcga:<http://purl.org/tcga/core#>',
			'select distinct ?url ?id where {',
			'	?id tcga:url ?url .',
			'	{',
			'		?id a tcga:DiseaseStudy .',
			'	} UNION {',
			'		?id a tcga:CenterType .',
			'	} UNION {',
			'		?id a tcga:CenterDomain .',
			'	} UNION {',
			'		?id a tcga:Platform .',
			'	} UNION {',
			'		?id a tcga:DataType .',
			'	} UNION {',
			'		?id a tcga:Archive .',
			'	}',
			'}'
		].join(' ');

		request({
				method : "POST",
				url : SPARQLURL+encodeURIComponent(query),
				headers : { "Accept" : "application/sparql-results+json" }
			}, function ( error, response, body ) {

				if (response.statusCode === 204) {
					console.log('Found no known entities.');
					return callback();
				}

				if (error || response.statusCode !== 200) {
					console.log("Unable to query SPARQL endpoint");
					return;
				}

				var bindings;

				bindings = JSON.parse(body).results.bindings;

				bindings.forEach(function (binding) {
					knownEntities[binding.url.value] = binding;
				});

				console.log('Found', bindings.length, 'entities.');

				return callback();
		});
	};

	getDiseaseFiles = function (disease, callback) {
		var query;

		diseaseFiles = {};

		query = [
			'prefix rdfs:<http://www.w3.org/2000/01/rdf-schema#>',
			'prefix tcga:<http://purl.org/tcga/core#>',
			'select distinct ?url ?id where {',
			'	?id tcga:url ?url .',
			'	?id tcga:diseaseStudy ?d .',
			'	?d rdfs:label "'+disease+'" .',
			'}'
		].join(' ');

		request({
				method : "POST",
				url : SPARQLURL+encodeURIComponent(query),
				headers : { "Accept" : "application/sparql-results+json" }
			}, function ( error, response, body ) {

				if (response.statusCode === 204) {
					console.log("Found no entities for", disease, ".");
					return callback();
				}

				if (error || response.statusCode !== 200) {
					console.log("Unable to query SPARQL endpoint for",disease,"files.");
					return;
				}

				var bindings;

				bindings = JSON.parse(body).results.bindings;

				bindings.forEach(function (binding) {
					diseaseFiles[binding.url.value] = binding;
				});

				console.log('Found', bindings.length, 'entities for', disease,'.');

				return callback();
		});

	};

	scrape = function (options) {
		var opts, target, parent, callback, start, callCallback;

		opts = options || {};
		target = opts.target || ROOT_URL;
		parent = opts.parent || {};

		callCallback = function () {
			if (callback && typeof callback === 'function') callback();
		};
		callback = opts.callback;

		request(target, function (error, response, body) {

			if (error || response.statusCode !== 200) {
				console.log("Failed to get", target);
				return callCallback();
			}

			else {

				var pre, rows, children = [], tcga, literal;

				tcga = function (name) { return "<http://purl.org/tcga/core#"+name+">"; };
				literal = function (value) { return '"'+value+'"'; };
				pre = body.match(/<pre>([\s\S]*)<\/pre>/);
				if (!pre) {
					console.log("Could not parse (pre)", target);
					return callCallback();
				} else pre = pre[1];
				rows = pre.match(/<a[^>]+>[^<]+<\/a>\s+[\d-]{10}/g);

				if (rows) rows.forEach(function (row) {

					var name, id, url, type, tripleString, lastModified, subject, scrapeChildren = true, level;

					name = row.match(/>([\s\S]*)</);
					if (!name) console.log("Could not parse (name)", row); else name = name[1];
					id = uuid();
					url = target + row.match(/href="(.*)"/)[1];
					level = target.split('/').length;
					type = types[level];
					try {
						lastModified = (new Date(row.match(/\s([\d-]{10})/)[1])).toISOString().slice(0,10);
					} catch (e) {
						console.log("Row:", row);
						process.exit(1);
					}
					tripleString = "";

					if ((process.env.TESTING && level === 10) || level >= 15) {
						scrapeChildren = false;
						if (target.match(/ucec|thca|stad/)) scrapeChildren = true;
					}

					if (name === 'lost+found/') return;

					name = name.trim();

					// Things with extensions are files (e.g. reallylong_name.tiff)
					// TODO Use MIME types to determine files.
					if (name.match(/^.*\.[^\/]+$/)) {
						type = types.file;
						scrapeChildren = false;
					}

					// Remove the trailing "/"
					if (name.charAt(name.length-1) === "/") name = name.slice(0,-1);

					// If a file ends in .tar.gz or .tar.gz.md5, type it as an archive
					if (type === types.file && name.match(/\.tar\.gz($|\.md5$)/)) {
						type = types.archive;
					}

					// If a file is of the form
					// <domain>_<disease study>.<platform>.<archive type>.<serial index>.<revision>.<series>
					// type it as an archive
					if (name.match(/.*?_(.*?\.){5}\d$/)) {
						type = types.archive;
					}

					// Either insert or update, based on whether this item exists in the known entities.
					if (!knownEntities[url] && !diseaseFiles[url]) {
						var entity = {
							id : { value : id },
							newEntity : true
						};
						if (type !== types.file) knownEntities[url] = entity;
						else diseaseFiles[url] = entity;
						subject = id.length === 36 ? tcga(id) : "<" + id + ">";
						tripleString = [
							subject, tcga("lastSeen"), literal(NOW), ".\n",
							subject, tcga("firstSeen"), literal(NOW), ".\n",
							subject, "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>",  tcga(type), ".\n",
							subject, "<http://www.w3.org/2000/01/rdf-schema#label>", literal(name), ".\n",
							subject, tcga("url"), literal(url), ".\n"
						];

						if (type === types.file || types === types.archive) {
							tripleString.push(subject, tcga("lastModified"), literal(lastModified), ".\n");
						}

						if (type === types.file) {
							var ancestor;
							Object.keys(parent).forEach(function (ancestor) {
								// Make the first letter lowercase
								var property = ancestor.charAt(0).toLowerCase() + ancestor.slice(1);
								tripleString.push(subject, tcga(property), parent[ancestor], ".\n");
							});
						}
					}
					else {
						if (knownEntities[url]) id = knownEntities[url].id.value;
						else id = diseaseFiles[url].id.value;
						subject = id.length === 36 ? tcga(id) : "<" + id + ">";
						tripleString = [
							subject, tcga("lastSeen"), literal(NOW), ".\n"
						];

						// Additional URLS may need to be added for new entities, such as center types.
						if (knownEntities[url] && knownEntities[url].newEntity) {
							tripleString.push(subject, tcga("url"), literal(url), ".\n");
						}
					}

					hub.publish('/triples', [{
						triples : tripleString.join(" "),
						type : type,
						name : name
					}]);

					if (scrapeChildren){
						var child;
						parent[type] = subject;
						child = {target:url, parent:JSON.parse(JSON.stringify(parent))};
						if (type == types.diseaseStudy) {
							child.type = type;
							child.name = name;
						}
						children.push(child);
					}

				});

				if (children.length > 0) {
					var counter = new Sync(children.length, callback);
					var nextChild = function nextChild(children){
						var child = children.pop();
						if (child){
							child.callback = function(){
								counter.decrement();
								nextChild(children);
							};
							if (child.type === types.diseaseStudy) {
								getDiseaseFiles(child.name, function () {
									scrape(child);
								});
							} else {
								scrape(child);
							}
						}
					};
					nextChild(children);
				}
				else callCallback();

			}

		});
	};

	//writer = Writer.getInstance();

	query = Query.getInstance().url(process.env.SPARQLURL);

	hub.subscribe('/triples', query.listen);

	//hub.subscribe('/triples', writer.listen);

	console.log('Getting known entities from hub.');

	getKnownEntities(function (err) {
		if (err) console.log(err);
		console.log('Beginning scrape of', ROOT_URL);
		scrape({
			target : ROOT_URL,
			callback : function () {
				Query.getInstance().flush();
				// writer.close();
				console.log("Scrape finished");
			}
		});
	});

})( exports );
<|endoftext|>


<commit_before>describe("multi-select collection deselecting", function(){
  var Model = Backbone.Model.extend({
    initialize: function(){
      var selectable = new Backbone.Picky.Selectable();
      _.extend(this, selectable);
    }
  });

  var Collection = Backbone.Collection.extend({
    model: Model,

    initialize: function(){
      var multiSelect = new Backbone.Picky.MultiSelect();
      _.extend(this, multiSelect);
    }
  });

  describe("when no models are selected, and deselecting all", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      spyOn(collection, "trigger").andCallThrough();

      collection.selectNone();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when 1 model is selected, and deselecting all", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      m1.select();

      spyOn(collection, "trigger").andCallThrough();
      collection.selectNone();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when all models are selected, and deselecting all", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      m1.select();
      m2.select();

      spyOn(collection, "trigger").andCallThrough();
      collection.selectNone();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

});
<commit_msg>Added tests for deselectAll<commit_after>describe("multi-select collection deselecting", function(){
  var Model = Backbone.Model.extend({
    initialize: function(){
      var selectable = new Backbone.Picky.Selectable();
      _.extend(this, selectable);
    }
  });

  var Collection = Backbone.Collection.extend({
    model: Model,

    initialize: function(){
      var multiSelect = new Backbone.Picky.MultiSelect();
      _.extend(this, multiSelect);
    }
  });

  describe("when no models are selected, and deselecting all (selectNone)", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      spyOn(collection, "trigger").andCallThrough();

      collection.selectNone();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when no models are selected, and deselecting all (deselectAll)", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      spyOn(collection, "trigger").andCallThrough();

      collection.deselectAll();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when 1 model is selected, and deselecting all (selectNone)", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      m1.select();

      spyOn(collection, "trigger").andCallThrough();
      collection.selectNone();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when 1 model is selected, and deselecting all (deselectAll)", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      m1.select();

      spyOn(collection, "trigger").andCallThrough();
      collection.deselectAll();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when all models are selected, and deselecting all (selectNone)", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      m1.select();
      m2.select();

      spyOn(collection, "trigger").andCallThrough();
      collection.selectNone();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

  describe("when all models are selected, and deselecting all (deselectAll)", function(){
    var m1, m2, collection;

    beforeEach(function(){
      m1 = new Model();
      m2 = new Model();

      collection = new Collection([m1, m2]);
      m1.select();
      m2.select();

      spyOn(collection, "trigger").andCallThrough();
      collection.deselectAll();
    });

    it("should trigger 'none' selected event", function(){
      expect(collection.trigger).toHaveBeenCalledWith("select:none", collection);
    });

    it("should have a selected count of 0", function(){
      expect(collection.selectedLength).toBe(0);
    });

    it("should not have any models in the selected list", function(){
      var size = _.size(collection.selected);
      expect(size).toBe(0);
    });
  });

});
<|endoftext|>


<commit_before>import { createSelector } from 'reselect';
import difference from 'lodash/difference';
import intersection from 'lodash/intersection';
import pluralize from 'pluralize';
import { arrayToSentence } from 'utils/utils';
import { GAS_AGGREGATES, GHG_CALCULATION_OPTIONS } from 'data/constants';
import {
  getFiltersSelected,
  getModelSelected,
  getBreakByOptionSelected,
  getChartTypeSelected,
  getMetricSelected
} from './ghg-emissions-selectors-filters';

const getOverlappingConflicts = optionsSelected => {
  if (!optionsSelected || optionsSelected.length <= 1) return [];

  const conflicts = [];
  const overlapsCheckedIds = [];

  optionsSelected.forEach(option => {
    if (option.expandsTo) {
      const overlappingOptions = [];
      optionsSelected.forEach(option2 => {
        if (option2.value === option.value) return;
        if (overlapsCheckedIds.includes(option2.value)) return;

        const expandedOption = [option.value, ...(option.expandsTo || [])].map(
          String
        );
        const expandedOption2 = [
          option2.value,
          option2.group,
          ...(option2.expandsTo || [])
        ].map(String);

        if (intersection(expandedOption, expandedOption2).length > 0) {
          overlappingOptions.push(option2.label);
        }
      });

      if (overlappingOptions.length) {
        conflicts.push(
          `${option.label} overlaps with ${arrayToSentence(overlappingOptions)}`
        );
      }

      overlapsCheckedIds.push(option.value);
    }
  });

  return conflicts;
};

const getGasConflicts = gasSelected => {
  const aggregatedGases = Object.keys(GAS_AGGREGATES);

  if (!gasSelected || gasSelected.length <= 1) return [];

  const conflicts = [];

  aggregatedGases
    .filter(g => g !== 'Aggregate F-gases')
    .forEach(gas => {
      if (gasSelected.some(g => g.label.includes(gas))) {
        conflicts.push(`${gas} option cannot be selected with any other gas`);
      }
    });

  return conflicts;
};

const getCalculationSectorConflicts = (sectorsSelected, metricSelected) => {
  const conflictMetrics = [
    GHG_CALCULATION_OPTIONS.PER_CAPITA.value,
    GHG_CALCULATION_OPTIONS.PER_GDP.value,
    GHG_CALCULATION_OPTIONS.CUMULATIVE.value
  ];
  return sectorsSelected &&
    sectorsSelected.length > 1 &&
    conflictMetrics.includes(metricSelected)
    ? [
      `More than one sector is not available with ${GHG_CALCULATION_OPTIONS[metricSelected].label} calculation`
    ]
    : [];
};

export const getIncompatibleSectorConflicts = (
  sectorsSelected,
  regionsSelected
) => {
  if (
    !regionsSelected ||
    !sectorsSelected ||
    !regionsSelected.some(r => r.iso === 'WORLD')
  ) {
    return [];
  }
  const sectorLabelsSelected = sectorsSelected.map(s => s.label);
  if (sectorLabelsSelected.includes('Bunker Fuels')) {
    const incompatibleSectors = intersection(sectorLabelsSelected, [
      'Energy',
      'Transportation'
    ]);
    if (incompatibleSectors) {
      return [
        `Bunker Fuels and ${incompatibleSectors.join(
          ', '
        )} sectors are not compatible with World region selected`
      ];
    }
  }
  return [];
};

const getCountryRegionConflicts = (placesSelected, breakBySelected) => {
  const regionsGroupSelected = placesSelected?.filter(
    r => r.groupId === 'regions'
  );
  const numberOfRegionsSelected = regionsGroupSelected?.length;
  const oneRegionAndCountriesSelected =
    numberOfRegionsSelected === 1 && placesSelected.length > 1;
  if (
    breakBySelected.value === 'countries' &&
    (numberOfRegionsSelected > 1 || oneRegionAndCountriesSelected)
  ) {
    return ['More than one region is selected'];
  }
  const onlyCountriesAreSelected =
    placesSelected?.length && numberOfRegionsSelected === 0;
  if (breakBySelected.value === 'regions' && onlyCountriesAreSelected) {
    return ['No region is selected'];
  }
  return [];
};

const getChartConflicts = (metricSelected, chartSelected, breakBySelected) => {
  const conflicts = [];

  if (
    ['PER_CAPITA', 'PER_GDP', 'PERCENTAGE_CHANGE'].includes(metricSelected) &&
    chartSelected.value !== 'line'
  ) {
    const metricOption = GHG_CALCULATION_OPTIONS[metricSelected];
    conflicts.push(
      `${metricOption.label} metric is not allowed with ${chartSelected.label} chart`
    );
  }

  if (
    ['PER_CAPITA', 'PER_GDP'].includes(metricSelected) &&
    ['sector', 'gas'].includes(breakBySelected.value)
  ) {
    const metricOption = GHG_CALCULATION_OPTIONS[metricSelected];
    conflicts.push(
      `${metricOption.label} metric is not allowed with show data by ${breakBySelected.label}`
    );
  }

  return conflicts;
};

export const getFiltersConflicts = createSelector(
  [
    getFiltersSelected('location'),
    getFiltersSelected('gas'),
    getFiltersSelected('sector'),
    getModelSelected,
    getMetricSelected,
    getChartTypeSelected,
    getBreakByOptionSelected
  ],
  (
    regionSelected,
    gasSelected,
    sectorsSelected,
    modelSelected,
    metricSelected,
    chartSelected,
    breakBySelected
  ) => {
    const isNotLineChart = chartSelected.value !== 'line';

    const sectorConflicts = getOverlappingConflicts(sectorsSelected);
    const incompatibleSectorConflicts = getIncompatibleSectorConflicts(
      sectorsSelected,
      regionSelected
    );
    const calculationSectorConflicts = getCalculationSectorConflicts(
      sectorsSelected,
      metricSelected
    );
    const regionConflicts = getOverlappingConflicts(regionSelected);
    const countryRegionConflicts = getCountryRegionConflicts(
      regionSelected,
      breakBySelected
    );
    const gasConflicts = getGasConflicts(gasSelected);
    const chartConflicts = getChartConflicts(
      metricSelected,
      chartSelected,
      breakBySelected
    );

    let conflicts = [];
    if (isNotLineChart || modelSelected !== 'sector') {
      conflicts = conflicts.concat(sectorConflicts);
    }
    conflicts = conflicts.concat(calculationSectorConflicts);
    conflicts = conflicts.concat(incompatibleSectorConflicts);

    if (isNotLineChart || modelSelected !== 'gas') {
      conflicts = conflicts.concat(gasConflicts);
    }
    if (isNotLineChart || modelSelected !== 'regions') {
      conflicts = conflicts.concat(regionConflicts);
    }
    conflicts = conflicts.concat(countryRegionConflicts);
    conflicts = conflicts.concat(chartConflicts);

    const getBreakByAvailableOptions = () => {
      if (
        incompatibleSectorConflicts.length ||
        calculationSectorConflicts.length ||
        chartConflicts.length
      ) {
        // Changing the show data by won't fix this cases
        return [];
      }
      let breakByAvailableOptions = difference(
        ['sector', 'gas', 'regions', 'countries'],
        [breakBySelected.value]
      );
      if (sectorConflicts.length) {
        breakByAvailableOptions = intersection(breakByAvailableOptions, [
          'sector'
        ]);
      }
      if (regionConflicts.length) {
        breakByAvailableOptions = intersection(breakByAvailableOptions, [
          'region'
        ]);
      }
      if (gasConflicts.length) {
        breakByAvailableOptions = intersection(breakByAvailableOptions, [
          'gas'
        ]);
      }
      return breakByAvailableOptions;
    };

    const solutions = [];
    const breakByAvailableOptions = getBreakByAvailableOptions();
    if (breakByAvailableOptions.length) {
      solutions.push(
        `change "Show data by" to ${arrayToSentence(
          breakByAvailableOptions.map(option => pluralize(option)),
          'or'
        )}`
      );
    }
    if (chartConflicts.length) {
      solutions.push('change "Chart Type" to line chart');
    }
    const solutionText = `Please deselect all conflicting options
        ${solutions.length ? `or ${solutions.join(' or ')}` : ''}`;

    return {
      conflicts,
      solutionText
    };
  }
);
<commit_msg>Fix conflict<commit_after>import { createSelector } from 'reselect';
import difference from 'lodash/difference';
import intersection from 'lodash/intersection';
import pluralize from 'pluralize';
import { arrayToSentence } from 'utils/utils';
import { GAS_AGGREGATES, GHG_CALCULATION_OPTIONS } from 'data/constants';
import {
  getFiltersSelected,
  getModelSelected,
  getBreakByOptionSelected,
  getChartTypeSelected,
  getMetricSelected
} from './ghg-emissions-selectors-filters';

const getOverlappingConflicts = optionsSelected => {
  if (!optionsSelected || optionsSelected.length <= 1) return [];

  const conflicts = [];
  const overlapsCheckedIds = [];

  optionsSelected.forEach(option => {
    if (option.expandsTo) {
      const overlappingOptions = [];
      optionsSelected.forEach(option2 => {
        if (option2.value === option.value) return;
        if (overlapsCheckedIds.includes(option2.value)) return;

        const expandedOption = [option.value, ...(option.expandsTo || [])].map(
          String
        );
        const expandedOption2 = [
          option2.value,
          option2.group,
          ...(option2.expandsTo || [])
        ].map(String);

        if (intersection(expandedOption, expandedOption2).length > 0) {
          overlappingOptions.push(option2.label);
        }
      });

      if (overlappingOptions.length) {
        conflicts.push(
          `${option.label} overlaps with ${arrayToSentence(overlappingOptions)}`
        );
      }

      overlapsCheckedIds.push(option.value);
    }
  });

  return conflicts;
};

const getGasConflicts = gasSelected => {
  const aggregatedGases = Object.keys(GAS_AGGREGATES);

  if (!gasSelected || gasSelected.length <= 1) return [];

  const conflicts = [];

  aggregatedGases
    .filter(g => g !== 'Aggregate F-gases')
    .forEach(gas => {
      if (gasSelected.some(g => g.label.includes(gas))) {
        conflicts.push(`${gas} option cannot be selected with any other gas`);
      }
    });

  return conflicts;
};

const getCalculationSectorConflicts = (sectorsSelected, metricSelected) => {
  const conflictMetrics = [
    GHG_CALCULATION_OPTIONS.PER_CAPITA.value,
    GHG_CALCULATION_OPTIONS.PER_GDP.value,
    GHG_CALCULATION_OPTIONS.CUMULATIVE.value
  ];
  return sectorsSelected &&
    sectorsSelected.length > 1 &&
    conflictMetrics.includes(metricSelected)
    ? [
      `More than one sector is not available with ${GHG_CALCULATION_OPTIONS[metricSelected].label} calculation`
    ]
    : [];
};

export const getIncompatibleSectorConflicts = (
  sectorsSelected,
  regionsSelected
) => {
  if (
    !regionsSelected ||
    !sectorsSelected ||
    !regionsSelected.some(r => r.iso === 'WORLD')
  ) {
    return [];
  }
  const sectorLabelsSelected = sectorsSelected.map(s => s.label);
  if (sectorLabelsSelected.includes('Bunker Fuels')) {
    const incompatibleSectors = intersection(sectorLabelsSelected, [
      'Energy',
      'Transportation'
    ]);

    if (incompatibleSectors.length) {
      return [
        `Bunker Fuels and ${incompatibleSectors.join(
          ', '
        )} sectors are not compatible with World region selected`
      ];
    }
  }
  return [];
};

const getCountryRegionConflicts = (placesSelected, breakBySelected) => {
  const regionsGroupSelected = placesSelected?.filter(
    r => r.groupId === 'regions'
  );
  const numberOfRegionsSelected = regionsGroupSelected?.length;
  const oneRegionAndCountriesSelected =
    numberOfRegionsSelected === 1 && placesSelected.length > 1;
  if (
    breakBySelected.value === 'countries' &&
    (numberOfRegionsSelected > 1 || oneRegionAndCountriesSelected)
  ) {
    return ['More than one region is selected'];
  }
  const onlyCountriesAreSelected =
    placesSelected?.length && numberOfRegionsSelected === 0;
  if (breakBySelected.value === 'regions' && onlyCountriesAreSelected) {
    return ['No region is selected'];
  }
  return [];
};

const getChartConflicts = (metricSelected, chartSelected, breakBySelected) => {
  const conflicts = [];

  if (
    ['PER_CAPITA', 'PER_GDP', 'PERCENTAGE_CHANGE'].includes(metricSelected) &&
    chartSelected.value !== 'line'
  ) {
    const metricOption = GHG_CALCULATION_OPTIONS[metricSelected];
    conflicts.push(
      `${metricOption.label} metric is not allowed with ${chartSelected.label} chart`
    );
  }

  if (
    ['PER_CAPITA', 'PER_GDP'].includes(metricSelected) &&
    ['sector', 'gas'].includes(breakBySelected.value)
  ) {
    const metricOption = GHG_CALCULATION_OPTIONS[metricSelected];
    conflicts.push(
      `${metricOption.label} metric is not allowed with show data by ${breakBySelected.label}`
    );
  }

  return conflicts;
};

export const getFiltersConflicts = createSelector(
  [
    getFiltersSelected('location'),
    getFiltersSelected('gas'),
    getFiltersSelected('sector'),
    getModelSelected,
    getMetricSelected,
    getChartTypeSelected,
    getBreakByOptionSelected
  ],
  (
    regionSelected,
    gasSelected,
    sectorsSelected,
    modelSelected,
    metricSelected,
    chartSelected,
    breakBySelected
  ) => {
    const isNotLineChart = chartSelected.value !== 'line';

    const sectorConflicts = getOverlappingConflicts(sectorsSelected);
    const incompatibleSectorConflicts = getIncompatibleSectorConflicts(
      sectorsSelected,
      regionSelected
    );
    const calculationSectorConflicts = getCalculationSectorConflicts(
      sectorsSelected,
      metricSelected
    );
    const regionConflicts = getOverlappingConflicts(regionSelected);
    const countryRegionConflicts = getCountryRegionConflicts(
      regionSelected,
      breakBySelected
    );
    const gasConflicts = getGasConflicts(gasSelected);
    const chartConflicts = getChartConflicts(
      metricSelected,
      chartSelected,
      breakBySelected
    );

    let conflicts = [];
    if (isNotLineChart || modelSelected !== 'sector') {
      conflicts = conflicts.concat(sectorConflicts);
    }
    conflicts = conflicts.concat(calculationSectorConflicts);
    conflicts = conflicts.concat(incompatibleSectorConflicts);

    if (isNotLineChart || modelSelected !== 'gas') {
      conflicts = conflicts.concat(gasConflicts);
    }
    if (isNotLineChart || modelSelected !== 'regions') {
      conflicts = conflicts.concat(regionConflicts);
    }
    conflicts = conflicts.concat(countryRegionConflicts);
    conflicts = conflicts.concat(chartConflicts);

    const getBreakByAvailableOptions = () => {
      if (
        incompatibleSectorConflicts.length ||
        calculationSectorConflicts.length ||
        chartConflicts.length
      ) {
        // Changing the show data by won't fix this cases
        return [];
      }
      let breakByAvailableOptions = difference(
        ['sector', 'gas', 'regions', 'countries'],
        [breakBySelected.value]
      );
      if (sectorConflicts.length) {
        breakByAvailableOptions = intersection(breakByAvailableOptions, [
          'sector'
        ]);
      }
      if (regionConflicts.length) {
        breakByAvailableOptions = intersection(breakByAvailableOptions, [
          'region'
        ]);
      }
      if (gasConflicts.length) {
        breakByAvailableOptions = intersection(breakByAvailableOptions, [
          'gas'
        ]);
      }
      return breakByAvailableOptions;
    };

    const solutions = [];
    const breakByAvailableOptions = getBreakByAvailableOptions();
    if (breakByAvailableOptions.length) {
      solutions.push(
        `change "Show data by" to ${arrayToSentence(
          breakByAvailableOptions.map(option => pluralize(option)),
          'or'
        )}`
      );
    }
    if (chartConflicts.length) {
      solutions.push('change "Chart Type" to line chart');
    }
    const solutionText = `Please deselect all conflicting options
        ${solutions.length ? `or ${solutions.join(' or ')}` : ''}`;

    return {
      conflicts,
      solutionText
    };
  }
);
<|endoftext|>


<commit_before>// TODO: IMPLEMENT ERRORS!

/*
 * Loader.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
/** @namespace data **/
define([
  '../sys',
  '../config',
  'EaselJS',
  'PreloadJS',
  './fs'
], function(
  sys,
  config,
  createjs,
  preloadjs,
  fs
) {
  /**
   *
   * An abstract data preloader used to cache files, preload and store them

   * dispatch progress events and complete events
   *
   * extends createjs.LoadQueue to simplify handling from outside

   *
   * @example
   *
   * var loader = new Loader();
   *
   * loader.addEventListener('complete', render);
   *
   * // can be an single file
   * loader.load('data.json');
   *
   * // can be an array of files
   * loader.load(['file1.jpg', 'file2.jpg']);
   *
   * // can be an array of files defined through id and src
   * loader.load([{id: 'firstFile', src: 'file1.jpg'}, {'id': 'secondFile': 'src': 'file2.jpg'}]);
   *
   * // can be a key / value pair object (NOTE: will lose the appropriate sequence order (i.e. files could be loaded randomly))
   * loader.load({firstFile: 'file1.jpg', secondFile: 'file2.jpg'});
   *
   * // to retrieve data just call:
   * loader.get('firstFile');
   *
   * // NOTE: when not passing id's array indices will be used
   * loader.get(0);
   *
   * // or to get all files as an array:
   * loader.get(); // NOTE: when only one file was loaded it will return the file directly instead of an array
   *
   * // can also be used statically to avoid event listeners overhead
   * Loader.load([files], complete, progress);
   *
   * @class Loader
   * @memberof data
   **/
  function Loader(){
    /**
     * stored items. better use the predefined class for getting elements
     * @see data.Loader.get
     * @memberof data.Loader
     * @instance
     * @var {object} items
     */
    this.items = [];

    /**
     * wheter Loader should overwrite files when fetching them (see data.fs.download)
     * @see data.fs
     * @memberof data.Loader
     * @instance
     * @var {Boolean} overwrite
     */
    this.overwrite = false;

    /**
     * instance load manifest which defines all the items that should be loaded

     * gets set via .load()
     * @memberof data.Loader
     * @instance
     * @private
     * @var {Array} _manifest
     */
    this._manifest = null;

    /**
     * cached items stored by manifest id as a key
     * @memberof data.Loader
     * @instance
     * @private
     * @var {Array} _results
     */
    this._results = [];

    /**
     * currently loaded index
     * @memberof data.Loader
     * @instance
     * @private
     * @var {Number} _loadIndex
     */
    this._loadIndex = 0;

    // initialize LoadQueue instance
    // load as tags for all applications except browser
    // (xhr does not work for file://paths)
    createjs.LoadQueue.call(this, config.environment === 'browser' ? true : false);

    // bind events right at the beginning so they get fired at first
    this.addEventListener('fileload', _fileload.bind(this));
  }

  var p = sys.extend(Loader, createjs.LoadQueue);

  /**
   * loads passed elements
   *
   * @method load
   * @memberof data.Loader
   * @instance
   * @public
   * @param {Object} manifest the files to load

   * can be an array of strings, a simple string, an array of objects (see createjs.LoadQueue)
   **/
  p.load = function(manifest){
    // make it an array if it is not
    if (typeof manifest === 'string') {
      manifest = [manifest];
    }

    // if manifest was an object
    if (typeof manifest === 'object' && ! Array.isArray(manifest)) {
      // if manifest was a single object with a source definition
      if (manifest.src) {
        // convert it to an array
        manifest = [manifest];
      } else {
        // otherwise: assume it was in the form {id: path} transform it to an array instead
        var tmp = manifest;
        manifest = [];

        for (var k in tmp) {
          if (tmp.hasOwnProperty(k)) {
            manifest.push({id: k, src: tmp[k]});
          }
        }
      }
    }

    // add id's to manifest items if not set
    for (var i = 0, _len = manifest.length; i < _len; i++) {
      if (typeof manifest[i] !== 'object') {
        manifest[i] = {id: i, src: manifest[i]};
      }
    }

    // store manifest for loading and caching purposes
    this._manifest = manifest;

    // start caching files in the filesystem (depending on the environment)
    _cacheNextFile.call(this);
  };

  /**
   * gets an element by its defined id
   * if no id was passed returns an array of all loaded elements
   * if there's only one element loaded returns it directly
   *
   * @method get
   * @memberof data.Loader
   * @instance
   * @public
   * @param {String|Number} id of the element to get
   **/
  p.get = function(id){
    // if there was only one item loaded return it directly
    // regardless passed id
    if (this.items.length === 1) {
      return this.items[0];
    }

    if (id === null || typeof id === 'undefined') {
      // if no id was passed return all loaded items instead
      return this.items;
    } else {
      // otherwise find loaded item by id
      for (var i = 0, _len = this._results.length; i < _len; i++) {
        if (this._results[i].item.id === id) {
          return this._results[i].result;
        }
      }
    }

    return null;
  };

  /**
   * called when a file has been loaded
   * stores file for later use in .get
   *
   * @method _fileload
   * @memberof data.Loader
   * @instance
   * @private
   **/
  var _fileload = function(event){
    this._results.push(event);
    this.items.push(event.result);
  };

  /**
   * caches file through fs (depending on environment)
   * when all files have been cached loads manifest file
   *
   * @method _cacheNextFile
   * @memberof data.Loader
   * @instance
   * @private
   **/
  var _cacheNextFile = function(){
    // if there's an open file that has not been cached yet
    if (this._manifest[this._loadIndex]) {
      // if it has a local file path
      if (this._manifest[this._loadIndex].local) {
        // download file
        var options = {};

        options.success = _receivedFilePath.bind(this);

        // if current item has an overwrite option
        if (! (this._manifest[this._loadIndex].overwrite === null || typeof this._manifest[this._loadIndex].overwrite === 'undefined')) {
          // use that definition
          options.overwrite = this._manifest[this._loadIndex].overwrite;
        } else {
          // otherwise use defined overwrite option in Loader
          options.overwrite = this.overwrite;
        }

        options.remote = this._manifest[this._loadIndex].src;
        options.local = this._manifest[this._loadIndex].local;
        fs.download(options);
      } else {
        // otherwise: assume it wants to load remote file
        _receivedFilePath.call(this, this._manifest[this._loadIndex].src);
      }
    } else {
      // if all files have been cached: preload cached files

      var _removedItem = false;

      // exclude items that should not be preloaded
      for (var i = 0, _len = this._manifest.length; i < _len; i++) {
        if (this._manifest[i].preload === false) {
          var evt = {};
          evt.item = {id: this._manifest[i].id};
          evt.result = this._manifest[i].src;
          this._results.push(evt);
          this.items.push(evt.result);

          this._manifest[i] = null;
          _removedItem = true;
        }
      }

      // restructure manifest items if items got removed
      if (_removedItem) {
        var _tmp = this._manifest;
        this._manifest = [];

        for (var k in _tmp) {
          if (_tmp[k] !== null && _tmp.hasOwnProperty(k)) {
            this._manifest.push(_tmp[k]);
          }
        }
      }

      // if there are still items left to load
      if (this._manifest.length > 0) {
        // load manifest
        this.loadManifest(this._manifest);
      } else {
        // otherwise: dispatch complete
        this.dispatchEvent(new createjs.Event('complete'));
      }
    }
  };

  /**
   * when a file has been cached and fs returned its new local file path
   *
   * @method _receivedFilePath
   * @memberof data.Loader
   * @instance
   * @private
   **/
  var _receivedFilePath = function(path){
    if (path) {
      // if it specified a local file path
      if (this._manifest[this._loadIndex].local) {
        // store correct file path (a relative file://path, same url in browser applications)
        // in manifest for later loading operations (i.e. only downloads file in nwjs applications)
        this._manifest[this._loadIndex].src = fs.getLocalFilePath(path);
      }

      // cache next file
      this._loadIndex++;

      // dispatch cached event
      var event = new createjs.Event('cached');
      event.progress = this._loadIndex / this._manifest.length;
      this.dispatchEvent(event);

      _cacheNextFile.call(this);
    } else {
      throw new Error('did not receive file path for ' + this._manifest[this._loadIndex]);
    }
  };

  /**
   * loads passed elements
   *
   * @method load
   * @memberof data.Loader
   * @param {Object} manifest the files to load

   * can be an array of strings, a simple string, an array of objects (see createjs.LoadQueue)
   * @param {function} complete callback when finished
   * @param {function} progress (optional) callback while loading data
   * @static
   **/
  Loader.load = function(manifest, complete, progress){
    var instance = new Loader();
    instance._callbackComplete = complete;
    instance._callbackProgress = progress;
    instance.addEventListener('complete', _instanceCompleted);
    instance.addEventListener('progress', _instanceProgress);
    instance.load(manifest);
  };

  /**
   * calls passed progress event when calling
   * Loader.load statically
   *
   * @method _instanceProgress
   * @memberof data.Loader
   * @private
   * @static
   **/
  var _instanceProgress = function(event){
    if (event.target._callbackProgress) event.target._callbackProgress.call(event.target, event);
  };

  /**
   * calls passed completed event when calling
   * Loader.load statically
   * passes event target data directly to callback (assumes it does not need event but only its loaded data)
   *
   * @method _instanceCompleted
   * @memberof data.Loader
   * @private
   * @static
   **/
  var _instanceCompleted = function(event){
    event.target.removeEventListener('complete', _instanceCompleted);
    event.target.removeEventListener('progress', _instanceProgress);
    if (event.target._callbackComplete) event.target._callbackComplete.call(event.target, event.target.get(), event);
  };

  return Loader;
});
<commit_msg>optionally allow setting checksum and filesize<commit_after>// TODO: IMPLEMENT ERRORS!

/*
 * Loader.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
/** @namespace data **/
define([
  '../sys',
  '../config',
  'EaselJS',
  'PreloadJS',
  './fs'
], function(
  sys,
  config,
  createjs,
  preloadjs,
  fs
) {
  /**
   *
   * An abstract data preloader used to cache files, preload and store them

   * dispatch progress events and complete events
   *
   * extends createjs.LoadQueue to simplify handling from outside

   *
   * @example
   *
   * var loader = new Loader();
   *
   * loader.addEventListener('complete', render);
   *
   * // can be an single file
   * loader.load('data.json');
   *
   * // can be an array of files
   * loader.load(['file1.jpg', 'file2.jpg']);
   *
   * // can be an array of files defined through id and src
   * loader.load([{id: 'firstFile', src: 'file1.jpg'}, {'id': 'secondFile': 'src': 'file2.jpg'}]);
   *
   * // can be a key / value pair object (NOTE: will lose the appropriate sequence order (i.e. files could be loaded randomly))
   * loader.load({firstFile: 'file1.jpg', secondFile: 'file2.jpg'});
   *
   * // to retrieve data just call:
   * loader.get('firstFile');
   *
   * // NOTE: when not passing id's array indices will be used
   * loader.get(0);
   *
   * // or to get all files as an array:
   * loader.get(); // NOTE: when only one file was loaded it will return the file directly instead of an array
   *
   * // can also be used statically to avoid event listeners overhead
   * Loader.load([files], complete, progress);
   *
   * @class Loader
   * @memberof data
   **/
  function Loader(){
    /**
     * stored items. better use the predefined class for getting elements
     * @see data.Loader.get
     * @memberof data.Loader
     * @instance
     * @var {object} items
     */
    this.items = [];

    /**
     * wheter Loader should overwrite files when fetching them (see data.fs.download)
     * @see data.fs
     * @memberof data.Loader
     * @instance
     * @var {Boolean} overwrite
     */
    this.overwrite = false;

    /**
     * instance load manifest which defines all the items that should be loaded

     * gets set via .load()
     * @memberof data.Loader
     * @instance
     * @private
     * @var {Array} _manifest
     */
    this._manifest = null;

    /**
     * cached items stored by manifest id as a key
     * @memberof data.Loader
     * @instance
     * @private
     * @var {Array} _results
     */
    this._results = [];

    /**
     * currently loaded index
     * @memberof data.Loader
     * @instance
     * @private
     * @var {Number} _loadIndex
     */
    this._loadIndex = 0;

    // initialize LoadQueue instance
    // load as tags for all applications except browser
    // (xhr does not work for file://paths)
    createjs.LoadQueue.call(this, config.environment === 'browser' ? true : false);

    // bind events right at the beginning so they get fired at first
    this.addEventListener('fileload', _fileload.bind(this));
  }

  var p = sys.extend(Loader, createjs.LoadQueue);

  /**
   * loads passed elements
   *
   * @method load
   * @memberof data.Loader
   * @instance
   * @public
   * @param {Object} manifest the files to load

   * can be an array of strings, a simple string, an array of objects (see createjs.LoadQueue)
   **/
  p.load = function(manifest){
    // make it an array if it is not
    if (typeof manifest === 'string') {
      manifest = [manifest];
    }

    // if manifest was an object
    if (typeof manifest === 'object' && ! Array.isArray(manifest)) {
      // if manifest was a single object with a source definition
      if (manifest.src) {
        // convert it to an array
        manifest = [manifest];
      } else {
        // otherwise: assume it was in the form {id: path} transform it to an array instead
        var tmp = manifest;
        manifest = [];

        for (var k in tmp) {
          if (tmp.hasOwnProperty(k)) {
            manifest.push({id: k, src: tmp[k]});
          }
        }
      }
    }

    // add id's to manifest items if not set
    for (var i = 0, _len = manifest.length; i < _len; i++) {
      if (typeof manifest[i] !== 'object') {
        manifest[i] = {id: i, src: manifest[i]};
      }
    }

    // store manifest for loading and caching purposes
    this._manifest = manifest;

    // start caching files in the filesystem (depending on the environment)
    _cacheNextFile.call(this);
  };

  /**
   * gets an element by its defined id
   * if no id was passed returns an array of all loaded elements
   * if there's only one element loaded returns it directly
   *
   * @method get
   * @memberof data.Loader
   * @instance
   * @public
   * @param {String|Number} id of the element to get
   **/
  p.get = function(id){
    // if there was only one item loaded return it directly
    // regardless passed id
    if (this.items.length === 1) {
      return this.items[0];
    }

    if (id === null || typeof id === 'undefined') {
      // if no id was passed return all loaded items instead
      return this.items;
    } else {
      // otherwise find loaded item by id
      for (var i = 0, _len = this._results.length; i < _len; i++) {
        if (this._results[i].item.id === id) {
          return this._results[i].result;
        }
      }
    }

    return null;
  };

  /**
   * called when a file has been loaded
   * stores file for later use in .get
   *
   * @method _fileload
   * @memberof data.Loader
   * @instance
   * @private
   **/
  var _fileload = function(event){
    this._results.push(event);
    this.items.push(event.result);
  };

  /**
   * caches file through fs (depending on environment)
   * when all files have been cached loads manifest file
   *
   * @method _cacheNextFile
   * @memberof data.Loader
   * @instance
   * @private
   **/
  var _cacheNextFile = function(){
    // if there's an open file that has not been cached yet
    if (this._manifest[this._loadIndex]) {
      // if it has a local file path
      if (this._manifest[this._loadIndex].local) {
        // download file
        var options = {};

        options.success = _receivedFilePath.bind(this);

        // if current item has an overwrite option
        if (! (this._manifest[this._loadIndex].overwrite === null || typeof this._manifest[this._loadIndex].overwrite === 'undefined')) {
          // use that definition
          options.overwrite = this._manifest[this._loadIndex].overwrite;
        } else {
          // otherwise use defined overwrite option in Loader
          options.overwrite = this.overwrite;
        }

        // merge in item's size if set
        if (this._manifest[this._loadIndex].size) {
          options.size = this._manifest[this._loadIndex].size;
        }

        // merge in item's checksum if set
        if (this._manifest[this._loadIndex].checksum) {
          options.checksum = this._manifest[this._loadIndex].checksum;
        }

        // set remote and local paths
        options.remote = this._manifest[this._loadIndex].src;
        options.local = this._manifest[this._loadIndex].local;

        // and start downloading file
        fs.download(options);
      } else {
        // otherwise: assume it wants to load remote file
        _receivedFilePath.call(this, this._manifest[this._loadIndex].src);
      }
    } else {
      // if all files have been cached: preload cached files

      var _removedItem = false;

      // exclude items that should not be preloaded
      for (var i = 0, _len = this._manifest.length; i < _len; i++) {
        if (this._manifest[i].preload === false) {
          var evt = {};
          evt.item = {id: this._manifest[i].id};
          evt.result = this._manifest[i].src;
          this._results.push(evt);
          this.items.push(evt.result);

          this._manifest[i] = null;
          _removedItem = true;
        }
      }

      // restructure manifest items if items got removed
      if (_removedItem) {
        var _tmp = this._manifest;
        this._manifest = [];

        for (var k in _tmp) {
          if (_tmp[k] !== null && _tmp.hasOwnProperty(k)) {
            this._manifest.push(_tmp[k]);
          }
        }
      }

      // if there are still items left to load
      if (this._manifest.length > 0) {
        // load manifest
        this.loadManifest(this._manifest);
      } else {
        // otherwise: dispatch complete
        this.dispatchEvent(new createjs.Event('complete'));
      }
    }
  };

  /**
   * when a file has been cached and fs returned its new local file path
   *
   * @method _receivedFilePath
   * @memberof data.Loader
   * @instance
   * @private
   **/
  var _receivedFilePath = function(path){
    if (path) {
      // if it specified a local file path
      if (this._manifest[this._loadIndex].local) {
        // store correct file path (a relative file://path, same url in browser applications)
        // in manifest for later loading operations (i.e. only downloads file in nwjs applications)
        this._manifest[this._loadIndex].src = fs.getLocalFilePath(path);
      }

      // cache next file
      this._loadIndex++;

      // dispatch cached event
      var event = new createjs.Event('cached');
      event.progress = this._loadIndex / this._manifest.length;
      this.dispatchEvent(event);

      _cacheNextFile.call(this);
    } else {
      throw new Error('did not receive file path for ' + this._manifest[this._loadIndex]);
    }
  };

  /**
   * loads passed elements
   *
   * @method load
   * @memberof data.Loader
   * @param {Object} manifest the files to load

   * can be an array of strings, a simple string, an array of objects (see createjs.LoadQueue)
   * @param {function} complete callback when finished
   * @param {function} progress (optional) callback while loading data
   * @static
   **/
  Loader.load = function(manifest, complete, progress){
    var instance = new Loader();
    instance._callbackComplete = complete;
    instance._callbackProgress = progress;
    instance.addEventListener('complete', _instanceCompleted);
    instance.addEventListener('progress', _instanceProgress);
    instance.load(manifest);
  };

  /**
   * calls passed progress event when calling
   * Loader.load statically
   *
   * @method _instanceProgress
   * @memberof data.Loader
   * @private
   * @static
   **/
  var _instanceProgress = function(event){
    if (event.target._callbackProgress) event.target._callbackProgress.call(event.target, event);
  };

  /**
   * calls passed completed event when calling
   * Loader.load statically
   * passes event target data directly to callback (assumes it does not need event but only its loaded data)
   *
   * @method _instanceCompleted
   * @memberof data.Loader
   * @private
   * @static
   **/
  var _instanceCompleted = function(event){
    event.target.removeEventListener('complete', _instanceCompleted);
    event.target.removeEventListener('progress', _instanceProgress);
    if (event.target._callbackComplete) event.target._callbackComplete.call(event.target, event.target.get(), event);
  };

  return Loader;
});
<|endoftext|>


<commit_before>'use strict';

const KNOWN_REDIRECTS = [
  {
    name: 'Google Search Results',
    targetParam: 'url',
    patterns: [
      '*://www.google.com/url?'
    ],
    // Google uses "ping" method sometimes.
    types: ["main_frame", "ping"]
  },
  {
    // Gmail wraps links in e-mails to pass you through their servers
    name: 'Gmail Link Wrappers',
    targetParam: 'q',
    patterns: [
      '*://www.google.com/url?'
    ],
    // I think that for Gmail, "main_frame" is enough.
    types: ["main_frame"]
  },
  {
    name: 'RedirectingAt',
    targetParam: 'url',
    patterns: [
      '*://*.redirectingat.com/?',
    ],
    types: ["main_frame"]
  },
  {
    name: 'Facebook',
    targetParam: 'u',
    patterns: [
        '*://l.facebook.com/l.php?',
        '*://l.messenger.com/l.php?'
    ],
    types: ["main_frame"]
  },
  {
    name: 'Amazon Affiliate',
    targetParam: 'location',
    patterns: [
      '*://*.amazon.ca/gp/redirect.html?'
    ],
    types: ["main_frame"]
  },
  {
    name: 'Rakuten Marketing',
    targetParam: 'murl',
    patterns: [
      '*://click.linksynergy.com/deeplink?'
    ],
    types: ["main_frame"]
  },
  {
    name: 'ValueClick',
    targetParam: 'url',
    patterns: [
      '*://www.dpbolvw.net/*?',
      '*://www.tkqlhce.com/*?'
    ],
    types: ["main_frame"]
  }
];

// Flip everything around a bit and store patterns that are looking for
// the same target all together. This way we can register these patterns
// using a closure-like approach to prevent having to scan the URL again
// to figure out which pattern it matched, and then finally extract the
// target for that pattern. Should result in things being much faster in
// then end.
// Use 'var' here so that it's not scoped incorrectly.
var REDIRECT_DATA_BY_TARGET_PARAM = {};

KNOWN_REDIRECTS.forEach(KNOWN_REDIRECT => {

  // Pluck out the param and the patterns
  const targetParam       = KNOWN_REDIRECT.targetParam;
  const orginalPatterns   = KNOWN_REDIRECT.patterns;
  const types             = KNOWN_REDIRECT.types;

  // Make sure everything looks good
  if (!(targetParam && orginalPatterns && orginalPatterns.length && types && types.length)) {
    return;
  }

  // Prep the Object if necessary
  if (!(REDIRECT_DATA_BY_TARGET_PARAM[targetParam])) {
    REDIRECT_DATA_BY_TARGET_PARAM[targetParam] = {
      patterns: [],
      types: []
    };
  }

  // Go through every "type" for this redirect
  types.forEach(type => {
    // If we don't already have this type for this target param, add it in
    if(!REDIRECT_DATA_BY_TARGET_PARAM[targetParam].types.includes(type)) {
      REDIRECT_DATA_BY_TARGET_PARAM[targetParam].types.push(type);
    }
  });

  const newPatterns = [];

  // Go through each of these patterns and create any combinations we need to
  orginalPatterns.forEach(originalPattern => {
    const targetParamKv = `${targetParam}=*`;

    // We need to generate a few variations on this original pattern
    // 1) support the URL param as the first param
    newPatterns.push(`${originalPattern}${targetParamKv}`);
    // 2) suppor the URL param as a non-first param
    newPatterns.push(`${originalPattern}*&${targetParamKv}`);
  });

  // Add these patterns to the array of patterns for this target param
  REDIRECT_DATA_BY_TARGET_PARAM[targetParam].patterns.push(...newPatterns);
});<commit_msg>Fixed indentation from 4 to 2 lines.<commit_after>'use strict';

const KNOWN_REDIRECTS = [
  {
    name: 'Google Search Results',
    targetParam: 'url',
    patterns: [
      '*://www.google.com/url?'
    ],
    // Google uses "ping" method sometimes.
    types: ["main_frame", "ping"]
  },
  {
    // Gmail wraps links in e-mails to pass you through their servers
    name: 'Gmail Link Wrappers',
    targetParam: 'q',
    patterns: [
      '*://www.google.com/url?'
    ],
    // I think that for Gmail, "main_frame" is enough.
    types: ["main_frame"]
  },
  {
    name: 'RedirectingAt',
    targetParam: 'url',
    patterns: [
      '*://*.redirectingat.com/?',
    ],
    types: ["main_frame"]
  },
  {
    name: 'Facebook',
    targetParam: 'u',
    patterns: [
      '*://l.facebook.com/l.php?',
      '*://l.messenger.com/l.php?'
    ],
    types: ["main_frame"]
  },
  {
    name: 'Amazon Affiliate',
    targetParam: 'location',
    patterns: [
      '*://*.amazon.ca/gp/redirect.html?'
    ],
    types: ["main_frame"]
  },
  {
    name: 'Rakuten Marketing',
    targetParam: 'murl',
    patterns: [
      '*://click.linksynergy.com/deeplink?'
    ],
    types: ["main_frame"]
  },
  {
    name: 'ValueClick',
    targetParam: 'url',
    patterns: [
      '*://www.dpbolvw.net/*?',
      '*://www.tkqlhce.com/*?'
    ],
    types: ["main_frame"]
  }
];

// Flip everything around a bit and store patterns that are looking for
// the same target all together. This way we can register these patterns
// using a closure-like approach to prevent having to scan the URL again
// to figure out which pattern it matched, and then finally extract the
// target for that pattern. Should result in things being much faster in
// then end.
// Use 'var' here so that it's not scoped incorrectly.
var REDIRECT_DATA_BY_TARGET_PARAM = {};

KNOWN_REDIRECTS.forEach(KNOWN_REDIRECT => {

  // Pluck out the param and the patterns
  const targetParam       = KNOWN_REDIRECT.targetParam;
  const orginalPatterns   = KNOWN_REDIRECT.patterns;
  const types             = KNOWN_REDIRECT.types;

  // Make sure everything looks good
  if (!(targetParam && orginalPatterns && orginalPatterns.length && types && types.length)) {
    return;
  }

  // Prep the Object if necessary
  if (!(REDIRECT_DATA_BY_TARGET_PARAM[targetParam])) {
    REDIRECT_DATA_BY_TARGET_PARAM[targetParam] = {
      patterns: [],
      types: []
    };
  }

  // Go through every "type" for this redirect
  types.forEach(type => {
    // If we don't already have this type for this target param, add it in
    if(!REDIRECT_DATA_BY_TARGET_PARAM[targetParam].types.includes(type)) {
      REDIRECT_DATA_BY_TARGET_PARAM[targetParam].types.push(type);
    }
  });

  const newPatterns = [];

  // Go through each of these patterns and create any combinations we need to
  orginalPatterns.forEach(originalPattern => {
    const targetParamKv = `${targetParam}=*`;

    // We need to generate a few variations on this original pattern
    // 1) support the URL param as the first param
    newPatterns.push(`${originalPattern}${targetParamKv}`);
    // 2) suppor the URL param as a non-first param
    newPatterns.push(`${originalPattern}*&${targetParamKv}`);
  });

  // Add these patterns to the array of patterns for this target param
  REDIRECT_DATA_BY_TARGET_PARAM[targetParam].patterns.push(...newPatterns);
});<|endoftext|>


<commit_before>define("rsvp",
  [],
  function() {
    "use strict";
    var browserGlobal = (typeof window !== 'undefined') ? window : {};

    var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
    var RSVP, async;

    if (typeof process !== 'undefined' &&
      {}.toString.call(process) === '[object process]') {
      async = function(callback, binding) {
        process.nextTick(function() {
          callback.call(binding);
        });
      };
    } else if (MutationObserver) {
      var queue = [];

      var observer = new MutationObserver(function() {
        var toProcess = queue.slice();
        queue = [];

        toProcess.forEach(function(tuple) {
          var callback = tuple[0], binding = tuple[1];
          callback.call(binding);
        });
      });

      var element = document.createElement('div');
      observer.observe(element, { attributes: true });

      // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
      window.addEventListener('unload', function(){
        observer.disconnect();
        observer = null;
      });

      async = function(callback, binding) {
        queue.push([callback, binding]);
        element.setAttribute('drainQueue', 'drainQueue');
      };
    } else {
      async = function(callback, binding) {
        setTimeout(function() {
          callback.call(binding);
        }, 1);
      };
    }

    var Event = function(type, options) {
      this.type = type;

      for (var option in options) {
        if (!options.hasOwnProperty(option)) { continue; }

        this[option] = options[option];
      }
    };

    var indexOf = function(callbacks, callback) {
      for (var i=0, l=callbacks.length; i<l; i++) {
        if (callbacks[i][0] === callback) { return i; }
      }

      return -1;
    };

    var callbacksFor = function(object) {
      var callbacks = object._promiseCallbacks;

      if (!callbacks) {
        callbacks = object._promiseCallbacks = {};
      }

      return callbacks;
    };

    var EventTarget = {
      mixin: function(object) {
        object.on = this.on;
        object.off = this.off;
        object.trigger = this.trigger;
        return object;
      },

      on: function(eventNames, callback, binding) {
        var allCallbacks = callbacksFor(this), callbacks, eventName;
        eventNames = eventNames.split(/\s+/);
        binding = binding || this;

        while (eventName = eventNames.shift()) {
          callbacks = allCallbacks[eventName];

          if (!callbacks) {
            callbacks = allCallbacks[eventName] = [];
          }

          if (indexOf(callbacks, callback) === -1) {
            callbacks.push([callback, binding]);
          }
        }
      },

      off: function(eventNames, callback) {
        var allCallbacks = callbacksFor(this), callbacks, eventName, index;
        eventNames = eventNames.split(/\s+/);

        while (eventName = eventNames.shift()) {
          if (!callback) {
            allCallbacks[eventName] = [];
            continue;
          }

          callbacks = allCallbacks[eventName];

          index = indexOf(callbacks, callback);

          if (index !== -1) { callbacks.splice(index, 1); }
        }
      },

      trigger: function(eventName, options) {
        var allCallbacks = callbacksFor(this),
            callbacks, callbackTuple, callback, binding, event;

        if (callbacks = allCallbacks[eventName]) {
          // Don't cache the callbacks.length since it may grow
          for (var i=0; i<callbacks.length; i++) {
            callbackTuple = callbacks[i];
            callback = callbackTuple[0];
            binding = callbackTuple[1];

            if (typeof options !== 'object') {
              options = { detail: options };
            }

            event = new Event(eventName, options);
            callback.call(binding, event);
          }
        }
      }
    };

    var Promise = function() {
      this.on('promise:resolved', function(event) {
        this.trigger('success', { detail: event.detail });
      }, this);

      this.on('promise:failed', function(event) {
        this.trigger('error', { detail: event.detail });
      }, this);
    };

    var noop = function() {};

    var invokeCallback = function(type, promise, callback, event) {
      var hasCallback = typeof callback === 'function',
          value, error, succeeded, failed;

      if (hasCallback) {
        try {
          value = callback(event.detail);
          succeeded = true;
        } catch(e) {
          failed = true;
          error = e;
        }
      } else {
        value = event.detail;
        succeeded = true;
      }

      if (value && typeof value.then === 'function') {
        value.then(function(value) {
          promise.resolve(value);
        }, function(error) {
          promise.reject(error);
        });
      } else if (hasCallback && succeeded) {
        promise.resolve(value);
      } else if (failed) {
        promise.reject(error);
      } else {
        promise[type](value);
      }
    };

    Promise.prototype = {
      then: function(done, fail) {
        var thenPromise = new Promise();

        if (this.isResolved) {
          RSVP.async(function() {
            invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue });
          }, this);
        }

        if (this.isRejected) {
          RSVP.async(function() {
            invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue });
          }, this);
        }

        this.on('promise:resolved', function(event) {
          invokeCallback('resolve', thenPromise, done, event);
        });

        this.on('promise:failed', function(event) {
          invokeCallback('reject', thenPromise, fail, event);
        });

        return thenPromise;
      },

      resolve: function(value) {
        resolve(this, value);

        this.resolve = noop;
        this.reject = noop;
      },

      reject: function(value) {
        reject(this, value);

        this.resolve = noop;
        this.reject = noop;
      }
    };

    function resolve(promise, value) {
      RSVP.async(function() {
        promise.trigger('promise:resolved', { detail: value });
        promise.isResolved = true;
        promise.resolvedValue = value;
      });
    }

    function reject(promise, value) {
      RSVP.async(function() {
        promise.trigger('promise:failed', { detail: value });
        promise.isRejected = true;
        promise.rejectedValue = value;
      });
    }

    function all(promises) {
    	var i, results = [];
    	var allPromise = new Promise();
    	var remaining = promises.length;

      if (remaining === 0) {
        allPromise.resolve([]);
      }

    	var resolver = function(index) {
    		return function(value) {
    			resolve(index, value);
    		};
    	};

    	var resolve = function(index, value) {
    		results[index] = value;
    		if (--remaining === 0) {
    			allPromise.resolve(results);
    		}
    	};

    	var reject = function(error) {
    		allPromise.reject(error);
    	};

    	for (i = 0; i < remaining; i++) {
    		promises[i].then(resolver(i), reject);
    	}
    	return allPromise;
    }

    EventTarget.mixin(Promise.prototype);

    RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true };
    return RSVP;
  });
<commit_msg>Remove tab indentation<commit_after>define("rsvp",
  [],
  function() {
    "use strict";
    var browserGlobal = (typeof window !== 'undefined') ? window : {};

    var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
    var RSVP, async;

    if (typeof process !== 'undefined' &&
      {}.toString.call(process) === '[object process]') {
      async = function(callback, binding) {
        process.nextTick(function() {
          callback.call(binding);
        });
      };
    } else if (MutationObserver) {
      var queue = [];

      var observer = new MutationObserver(function() {
        var toProcess = queue.slice();
        queue = [];

        toProcess.forEach(function(tuple) {
          var callback = tuple[0], binding = tuple[1];
          callback.call(binding);
        });
      });

      var element = document.createElement('div');
      observer.observe(element, { attributes: true });

      // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
      window.addEventListener('unload', function(){
        observer.disconnect();
        observer = null;
      });

      async = function(callback, binding) {
        queue.push([callback, binding]);
        element.setAttribute('drainQueue', 'drainQueue');
      };
    } else {
      async = function(callback, binding) {
        setTimeout(function() {
          callback.call(binding);
        }, 1);
      };
    }

    var Event = function(type, options) {
      this.type = type;

      for (var option in options) {
        if (!options.hasOwnProperty(option)) { continue; }

        this[option] = options[option];
      }
    };

    var indexOf = function(callbacks, callback) {
      for (var i=0, l=callbacks.length; i<l; i++) {
        if (callbacks[i][0] === callback) { return i; }
      }

      return -1;
    };

    var callbacksFor = function(object) {
      var callbacks = object._promiseCallbacks;

      if (!callbacks) {
        callbacks = object._promiseCallbacks = {};
      }

      return callbacks;
    };

    var EventTarget = {
      mixin: function(object) {
        object.on = this.on;
        object.off = this.off;
        object.trigger = this.trigger;
        return object;
      },

      on: function(eventNames, callback, binding) {
        var allCallbacks = callbacksFor(this), callbacks, eventName;
        eventNames = eventNames.split(/\s+/);
        binding = binding || this;

        while (eventName = eventNames.shift()) {
          callbacks = allCallbacks[eventName];

          if (!callbacks) {
            callbacks = allCallbacks[eventName] = [];
          }

          if (indexOf(callbacks, callback) === -1) {
            callbacks.push([callback, binding]);
          }
        }
      },

      off: function(eventNames, callback) {
        var allCallbacks = callbacksFor(this), callbacks, eventName, index;
        eventNames = eventNames.split(/\s+/);

        while (eventName = eventNames.shift()) {
          if (!callback) {
            allCallbacks[eventName] = [];
            continue;
          }

          callbacks = allCallbacks[eventName];

          index = indexOf(callbacks, callback);

          if (index !== -1) { callbacks.splice(index, 1); }
        }
      },

      trigger: function(eventName, options) {
        var allCallbacks = callbacksFor(this),
            callbacks, callbackTuple, callback, binding, event;

        if (callbacks = allCallbacks[eventName]) {
          // Don't cache the callbacks.length since it may grow
          for (var i=0; i<callbacks.length; i++) {
            callbackTuple = callbacks[i];
            callback = callbackTuple[0];
            binding = callbackTuple[1];

            if (typeof options !== 'object') {
              options = { detail: options };
            }

            event = new Event(eventName, options);
            callback.call(binding, event);
          }
        }
      }
    };

    var Promise = function() {
      this.on('promise:resolved', function(event) {
        this.trigger('success', { detail: event.detail });
      }, this);

      this.on('promise:failed', function(event) {
        this.trigger('error', { detail: event.detail });
      }, this);
    };

    var noop = function() {};

    var invokeCallback = function(type, promise, callback, event) {
      var hasCallback = typeof callback === 'function',
          value, error, succeeded, failed;

      if (hasCallback) {
        try {
          value = callback(event.detail);
          succeeded = true;
        } catch(e) {
          failed = true;
          error = e;
        }
      } else {
        value = event.detail;
        succeeded = true;
      }

      if (value && typeof value.then === 'function') {
        value.then(function(value) {
          promise.resolve(value);
        }, function(error) {
          promise.reject(error);
        });
      } else if (hasCallback && succeeded) {
        promise.resolve(value);
      } else if (failed) {
        promise.reject(error);
      } else {
        promise[type](value);
      }
    };

    Promise.prototype = {
      then: function(done, fail) {
        var thenPromise = new Promise();

        if (this.isResolved) {
          RSVP.async(function() {
            invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue });
          }, this);
        }

        if (this.isRejected) {
          RSVP.async(function() {
            invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue });
          }, this);
        }

        this.on('promise:resolved', function(event) {
          invokeCallback('resolve', thenPromise, done, event);
        });

        this.on('promise:failed', function(event) {
          invokeCallback('reject', thenPromise, fail, event);
        });

        return thenPromise;
      },

      resolve: function(value) {
        resolve(this, value);

        this.resolve = noop;
        this.reject = noop;
      },

      reject: function(value) {
        reject(this, value);

        this.resolve = noop;
        this.reject = noop;
      }
    };

    function resolve(promise, value) {
      RSVP.async(function() {
        promise.trigger('promise:resolved', { detail: value });
        promise.isResolved = true;
        promise.resolvedValue = value;
      });
    }

    function reject(promise, value) {
      RSVP.async(function() {
        promise.trigger('promise:failed', { detail: value });
        promise.isRejected = true;
        promise.rejectedValue = value;
      });
    }

    function all(promises) {
      var i, results = [];
      var allPromise = new Promise();
      var remaining = promises.length;

      if (remaining === 0) {
        allPromise.resolve([]);
      }

      var resolver = function(index) {
        return function(value) {
          resolve(index, value);
        };
      };

      var resolve = function(index, value) {
        results[index] = value;
        if (--remaining === 0) {
          allPromise.resolve(results);
        }
      };

      var reject = function(error) {
        allPromise.reject(error);
      };

      for (i = 0; i < remaining; i++) {
        promises[i].then(resolver(i), reject);
      }
      return allPromise;
    }

    EventTarget.mixin(Promise.prototype);

    RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true };
    return RSVP;
  });
<|endoftext|>


<commit_before>function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    },
  }
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type)

  const isProperty = key => key !== "children"
  Object.keys(fiber.props)
    .filter(isProperty)
    .forEach(name => {
      dom[name] = fiber.props[name]
    })

  return dom
}

function commitRoot() {
  // TODO
}

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  }
  nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let wipRoot = null

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }

  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }

  const elements = fiber.props.children
  let index = 0
  let prevSibling = null

  while (index < elements.length) {
    const element = elements[index]

    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    }

    if (index === 0) {
      fiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }

    prevSibling = newFiber
    index++
  }

  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

const Didact = {
  createElement,
  render,
}

/** @jsx Didact.createElement */
const element = (

    bar

)
const container = document.getElementById("root")
Didact.render(element, container)
<commit_msg>Commit work<commit_after>function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    },
  }
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type)

  const isProperty = key => key !== "children"
  Object.keys(fiber.props)
    .filter(isProperty)
    .forEach(name => {
      dom[name] = fiber.props[name]
    })

  return dom
}

function commitRoot() {
  commitWork(wipRoot.child)
  wipRoot = null
}

function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  domParent.appendChild(fiber.dom)
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  }
  nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let wipRoot = null

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }

  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }

  const elements = fiber.props.children
  let index = 0
  let prevSibling = null

  while (index < elements.length) {
    const element = elements[index]

    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    }

    if (index === 0) {
      fiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }

    prevSibling = newFiber
    index++
  }

  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

const Didact = {
  createElement,
  render,
}

/** @jsx Didact.createElement */
const element = (

    bar

)
const container = document.getElementById("root")
Didact.render(element, container)
<|endoftext|>