<commit_before>request = require('request');
cheerio = require('cheerio');
q = require('q');

var boardgamesQueue = [];
var boardgames = [];
var geeklists = [];
var boardgameStats = [];
var geeklistStats = [];
var promises = [];
var bg_promises = [];

function getGeeklistData(geeklistId){
	var url = 'https://www.boardgamegeek.com/xmlapi/geeklist/';
	var p = q.defer();
	var promises = [];

	//promises.push(p.promise);

	request(url + geeklistId, function(error, response, body){
		if(!error && response.statusCode == 200){
			var $ = cheerio.load(response.body);
			$('item').each(function(index, element){
				if($(this).attr('objecttype') == 'thing'){
					if($(this).attr('subtype') == 'boardgame'){
						var bgId = $(this).attr('objectid');
						var bg = boardgamesQueue.filter(function(e){
							return e.objectid == bgId;
						});

						if(bg.length == 0){
							boardgamesQueue.push({objectid: bgId});
						}
					}
				}else if($(this).attr('objecttype') == 'geeklist'){
						var glId = $(this).attr('id');
						var gl = geeklists.filter(function(e){
							return e.objectid == geeklistId
						});

						if(gl.length == 0){
							console.log('Loading list: ' + glId);
							geeklists.push({objectid: glId});

							promises.push(getGeeklistData(glId));
						}else{
							//Load precalculated data
							//if it exists - if not, it is a
							//circular dependency and will be
							//discarded.
						}
				}
			});

			q.all(promises).then(
				function(){
					p.resolve();
				}
			);
		}else{
			console.log('noting found or error!');
		}
	});

	return p.promise
}

function getBoardgameData(boardgameId){
	var url = 'https://www.boardgamegeek.com/xmlapi/boardgame/';

	//Here, we should check the database first
	//then check if it is complete (if not, reload)
	//If not found, fetch from boardgamegeek

	var p = q.defer();

	//TODO:
	//Send request to database: if found then resolve, else reject

	request(url + boardgameId, function(error, response, body){
		if(!error && response.statusCode == 200){
			var bg = {};
			var $ = cheerio.load(response.body);
			bg['objectid'] = boardgameId;
			bg['yearpublished'] = $('yearpublished').text();
			bg['minplayers'] = $('minplayers').text();
			bg['maxplayers'] = $('maxplayers').text();
			bg['playingtime'] = $('playingtime').text();
			bg['name'] = [];
			$('name').each(function(index, elem){
				bg['name'].push({'name': $(this).text(), 'primary': $(this).attr('primary')});
			});
			bg['thumbnail'] = $('thumbnail').text();
			bg['boardgamecategory'] = [];
			$('boardgamecategory').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamecategory'].push({objectid: id, name: val});
			});
			bg['boardgamemechanic'] = [];
			$('boardgamemechanic').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamemechanic'].push({objectid: id, name: val});
			});
			bg['boardgamedesigner'] = [];
			$('boardgamedesigner').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamedesigner'].push({objectid: id, name: val});
			});
			bg['boardgameartist'] = [];
			$('boardgameartist').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgameartist'].push({objectid: id, name: val});
			});
			bg['boardgamepublisher'] = [];
			$('boardgamepublisher').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamepublisher'].push({objectid: id, name: val});
			});
		}

		return p.resolve(bg)
	});

	return p.promise
}

//This needs to be a promise.
//getGeeklistData(174437);

//q.all(
//	promises
//).then(
getGeeklistData(174437).then(
	function(){
		console.log("queue length" + boardgamesQueue.length);
		var bg_promises = [];

		boardgamesQueue.forEach(function(bg, idx){
			var p = getBoardgameData(bg.objectid).then(
				function(v){
					bg = v;
					boardgames.push(bg);
					console.log(JSON.stringify(bg));
				});

			bg_promises.push(p);
		});

		console.log("geeklist promises done!");
		return q.all(bg_promises)
	}
).then(
	//TODO: Save boardgames to database
	//TODO: Delete same-date and save geeklistStats to database
	//TODO: Delete same-date and save boardgameStats to database
  //TODO: Calculate geeklistSnapshotStats (mechanics breakdown, drilldowns).
	//TODO: Delete and save geeklistSnapshotStats
	function(){
		boardgames.forEach(function(bg, i){
			console.log(JSON.stringify(bg));
		});
		console.log(boardgames.length);
	}

).done(
	function(){
		console.log("All done");
	}
);

geeklist = function(){
	return {
		objectid: null,
		geeklistGroup: "",
		update: false
	}
}
<commit_msg> - Added crude saving to couchdb<commit_after>request = require('request');
cheerio = require('cheerio');
q = require('q');

var boardgamesQueue = [];
var boardgames = [];
var geeklists = [];
var boardgameStats = [];
var geeklistStats = [];

var dbURL = "http://127.0.0.1:5984";
var geeklistURL = 'https://www.boardgamegeek.com/xmlapi/geeklist/';
var boardgameURL = 'https://www.boardgamegeek.com/xmlapi/boardgame/';
var boardgameViewURL = dbURL + '/geeklistmon/_design/boardgame/_view/boardgame?include_docs=true&key='

//Tries to implement q promises for requests
function qrequest(method, url, data){
	var p = q.defer();

	if(method.toUpperCase() === "GET"){
		requests(url, function(error, response, body){
			if(!error && response.statusCode == 200){
				p.resolve(body);
			}else{
				p.reject(body);
			}
		});
	}else if(method.toUpperCase() === "PUT"){

	}

	return p.promise
}

function getGeeklistData(geeklistId){
	var p = q.defer();
	var promises = [];

	//var boardgamesQueue = [];

	request(geeklistURL + geeklistId, function(error, response, body){
		if(!error && response.statusCode == 200){
			var $ = cheerio.load(response.body);
			$('item').each(function(index, element){
				if($(this).attr('objecttype') == 'thing'){
					if($(this).attr('subtype') == 'boardgame'){
						var bgId = $(this).attr('objectid');
						var bg = boardgamesQueue.filter(function(e){
							return e.objectid == bgId;
						});

						if(bg.length == 0){
							boardgamesQueue.push({objectid: bgId});
						}

						//TODO: Add boardgameStats here.
						//Counts for this geeklist, number of thumbs
						//and other useful info.
					}
				}else if($(this).attr('objecttype') == 'geeklist'){
						var glId = $(this).attr('id');
						var gl = geeklists.filter(function(e){
							return e.objectid == geeklistId
						});

						if(gl.length == 0){
							console.log('Loading list: ' + glId);
							geeklists.push({objectid: glId});

							promises.push(getGeeklistData(glId));
						}else{
							//Load precalculated data
							//if it exists - if not, it is a
							//circular dependency and will be
							//discarded.
						}
				}
			});

			q.all(promises).then(
				function(){
					//Should be
					//p.resolve(boardgameQueue);
					p.resolve();
				}
			);
		}else{
			console.log('noting found or error!');
		}
	});

	return p.promise
}

function getBoardgameData(boardgameId){

	//Here, we should check the database first
	//then check if it is complete (if not, reload)
	//If not found, fetch from boardgamegeek

	var p = q.defer();

	//TODO:
	//Send request to database: if found then resolve, else reject
	//qrequest(boardgameViewURL + "\"" + boardgameId + "\"").then(
	//	function(res){
	//
	//	}
	//).

	request(boardgameURL + boardgameId, function(error, response, body){
		if(!error && response.statusCode == 200){
			var bg = {};
			var $ = cheerio.load(response.body);

			bg['type'] = "boardgame";
			bg['objectid'] = boardgameId;
			bg['yearpublished'] = $('yearpublished').text();
			bg['minplayers'] = $('minplayers').text();
			bg['maxplayers'] = $('maxplayers').text();
			bg['playingtime'] = $('playingtime').text();
			bg['thumbnail'] = $('thumbnail').text();

			bg['name'] = [];
			$('name').each(function(index, elem){
				bg['name'].push({'name': $(this).text(), 'primary': $(this).attr('primary')});
			});

			bg['boardgamecategory'] = [];
			$('boardgamecategory').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamecategory'].push({objectid: id, name: val});
			});

			bg['boardgamemechanic'] = [];
			$('boardgamemechanic').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamemechanic'].push({objectid: id, name: val});
			});
			bg['boardgamedesigner'] = [];
			$('boardgamedesigner').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamedesigner'].push({objectid: id, name: val});
			});
			bg['boardgameartist'] = [];
			$('boardgameartist').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgameartist'].push({objectid: id, name: val});
			});
			bg['boardgamepublisher'] = [];
			$('boardgamepublisher').each(function(index, elem){
				var id = $(this).attr('objectid');
				var val = $(this).text();
				bg['boardgamepublisher'].push({objectid: id, name: val});
			});
		}

		return p.resolve(bg)
	});

	return p.promise
}

/*
 * Run main algorithm
 */
getGeeklistData(174437).then(
	//TODO: Calculate and save geeklist stats
	function(){
		var bg_promises = [];

		boardgamesQueue.forEach(function(bg, idx){
			var p = getBoardgameData(bg.objectid).then(
				function(v){
					bg = v;
					boardgames.push(bg);
				});

			bg_promises.push(p);
		});

		return q.all(bg_promises)
	}
).then(
	function(){
		var p = q.defer();
		var idURL = dbURL + '\\_uuids?count=' + boardgames.length;

		console.log(idURL);

		request(idURL, function(error, response, body){
			if(!error && response.statusCode == 200){
				p.resolve(JSON.parse(body).uuids);
			}else{
				console.log("error: " + response.statusCode);
				console.log("error: " + body);
			}
		});

		return p.promise
	}
).then(
	//TODO: Save boardgames to database
	//TODO: Delete same-date and save geeklistStats to database
	//TODO: Delete same-date and save boardgameStats to database
  //TODO: Calculate geeklistSnapshotStats (mechanics breakdown, drilldowns).
	//TODO: Delete and save geeklistSnapshotStats
	function(uuids){
		var promises = [];

		//Use bulk saving instead
		boardgames.forEach(function(bg, i){
			var p = q.defer();
			var docId = uuids.pop();
			var docURL = dbURL + "\\geeklistmon\\" + docId;

			console.log("Saving " + bg.objectid);

			request(
				{
					method: "PUT",
					uri: docURL,
					body: JSON.stringify(bg)
				},
				function(error, response, body) {
					if(response.statusCode == 201){
						console.log("bg saved: "+ docId);
						var reply = JSON.parse(body);

						if(reply.ok){
							bg["_id"] = reply.id;
							bg["_rev"] = reply.rev;

							p.resolve();
						}else{
							p.reject("DB failed to save");
						}

						//p.resolve();
					}else{
						console.log("error: " + response.statusCode);
						console.log("error: " + body);
						p.reject(body);
					}
				}
			)

			promises.push(p.promise);
		});

		return q.all(promises)
	}
).done(
	function(){
		boardgames.forEach(function(bg, idx){
			console.log(bg);
		});
		console.log("All done");
	}
);

//Classes - just for reference
geeklist = function(){
	return {
		objectid: null,
		geeklistGroup: "",
		update: false
	}
}
<|endoftext|>


<commit_before>'use strict';

var getUrls = require('get-urls')
  , iterate = require('array-iterate')
  , isUrl = require('is-url')
  , getTitleAtUrl = require('get-title-at-url')
  , Promise = require("Promise")
  , parseDomain = require("parse-domain")
	, he = require('he')
  , exports = module.exports = {}
  ;

function replaceLinkAtPosition(markdown, urlTitleMarkdownPair, position) {
	 var linkToReplace = urlTitleMarkdownPair.url
	   , newLinkMarkdown = urlTitleMarkdownPair.titleMarkdown
		 , linkLength = linkToReplace.length
	   , prefix = markdown.substring(0, position)
	   , afterStartPosition = position + linkLength
	   , postfix = markdown.substring(afterStartPosition, markdown.length)
	   , result = prefix + newLinkMarkdown + postfix
	   ;

	 return result;
}

function generateTitleMarkdown(url, title, source) {
	return '"[' + title + '](' + url + ')", *' + source + '*';
}

function geneneratePromiseForTitleLookup(url, options) {
	if (options.d && options.d.indexOf && options.d.indexOf("urlresponse") !== -1) {
		console.log("Creating promise to find url: " + url);
	}

	 return new Promise(function (resolve, reject) {

		getTitleAtUrl(url, function (title) {
			if (options.d && options.d.indexOf && options.d.indexOf("urlresponse") !== -1) {
				console.log("title: " + title + " found for url: " + url);
			}

			if (!title) {
				resolve();
				return;
			}

			var domainData = parseDomain(url)
				, source = domainData.domain + "." + domainData.tld
				, urlTitleMarkdownPair = {
					url: url,
					titleMarkdown: generateTitleMarkdown(url, title, source)
				};

			resolve(urlTitleMarkdownPair);
		});
	});

}

function filterValidUrlsAndLookupTitles(urls, markdown, options) {
	var titleLookupPromises = []
		, relookupFailure = false
		, loweredMarkdown = markdown.toLowerCase()
		;

	iterate(urls, function (currentUrl) {
		var urlIndexTracker = 0;

		if (options.d) {
			var nextIndexOf = loweredMarkdown.indexOf(currentUrl, urlIndexTracker);
			if (nextIndexOf === -1) {
				console.log("\nCould not find url: " + currentUrl + " that was previously found in the markdown during parsing, urlIndexTracker: " + urlIndexTracker);
			}
		}
		while (loweredMarkdown.indexOf(currentUrl, urlIndexTracker) !== -1) {
			var currentUrlIndex = urlIndexTracker;
			var currentUrlStart = loweredMarkdown.indexOf(currentUrl, currentUrlIndex);
			urlIndexTracker = currentUrlStart + currentUrl.length;

			if ((currentUrlStart + currentUrl.length) < markdown.length) {
				var endOfPlusOneUrl = currentUrlStart + currentUrl.length + 1;
				var urlPlusOneChar = markdown.substring(currentUrlStart, endOfPlusOneUrl);

				if (!urlPlusOneChar.endsWith(")") && isUrl(urlPlusOneChar)) {
					continue;
				}

				if (currentUrlStart > 2) {
					var positionTwoCharactersBack = currentUrlStart - 2;
					var precendingString = markdown.substring(positionTwoCharactersBack, currentUrlStart);

					if (precendingString === "](") {
						continue;
					}
				}

				if (currentUrlStart > 3) {
					var precendingString = markdown.substring(currentUrlStart - 3, currentUrlStart);

					if (precendingString === "]: ") {
						continue;
					}
				}
			}

			var titleLookupPromise = geneneratePromiseForTitleLookup(currentUrl, options);

			titleLookupPromises.push(titleLookupPromise);
			break;
		}
	});

	return titleLookupPromises;
}
function executeReplacePlainLinksWithTitles(urlTitleList, markdown, callback, options) {
	iterate(urlTitleList, function (urlTitleMarkdownPair) {
		if (!urlTitleMarkdownPair) {
			return true;
		}

		var currentUrl = urlTitleMarkdownPair.url
			, urlIndexTracker = 0
			;

		if (!currentUrl) {
			return true;
		}

		if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
			console.log("Attempting to replace url: " + currentUrl);
			var nextIndexOf = markdown.indexOf(currentUrl, urlIndexTracker);
			console.log("Next index of url: " + currentUrl + " is at position: " + nextIndexOf + " during replacement");
		}
		while (markdown.indexOf(currentUrl, urlIndexTracker) !== -1) {
			var currentUrlIndex = urlIndexTracker;
			var currentUrlStart = markdown.indexOf(currentUrl, currentUrlIndex);
			if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
				console.log("Looking for url during replacement: " + currentUrl + " after position: " + currentUrlIndex);
			}
			if ((currentUrlStart + currentUrl.length) < markdown.length) {
				var endOfPlusOneUrl = currentUrlStart + currentUrl.length + 1;
				var urlPlusOneChar = markdown.substring(currentUrlStart, endOfPlusOneUrl);

				if (!urlPlusOneChar.endsWith(")") && isUrl(urlPlusOneChar)) {
					urlIndexTracker = currentUrlStart + currentUrl.length;
					if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
						console.log("Skipping replacement of url: " + currentUrl + " for short version of url.");
					}
					continue;
				}

				if (currentUrlStart > 2) {
					var precendingString = markdown.substring(currentUrlStart - 2, currentUrlStart);

					if (precendingString === "](") {
						urlIndexTracker = currentUrlStart + currentUrl.length;
						if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
							console.log("Skipping replacement of url: " + currentUrl + "  for ]( prefix.");
						}
						continue;
					}
				}

				if (currentUrlStart > 3) {
					var precendingString = markdown.substring(currentUrlStart - 3, currentUrlStart);

					if (precendingString === "]: ") {
						urlIndexTracker = currentUrlStart + currentUrl.length;
						if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
							console.log("Skipping replacement of url: " + currentUrl + " for ]: prefix.");
						}
						continue;
					}
				}
			}
			markdown = replaceLinkAtPosition(markdown, urlTitleMarkdownPair, currentUrlStart);
			urlIndexTracker = currentUrlStart + urlTitleMarkdownPair.titleMarkdown.length;
		}
	});

	callback(markdown);
}

function getUrlsAndFilter(markdown, options) {
	var urls = getUrls(markdown, { stripWWW: false, stripFragment: false, normalizeProtocol: false, removeQueryParameters: [] })
		, outputResults = []
		;

	if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("getUrls output:");
				console.log(urls);
	}
	iterate(urls, function (currentUrl) {
		if (!currentUrl) {
			return true;
		}

		if (currentUrl.endsWith(")") && currentUrl.indexOf("(") === -1) {
			currentUrl = currentUrl.substring(0, currentUrl.length - 1);
			if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("new url: " + currentUrl);
			}
		}

		if (currentUrl.toLowerCase().endsWith(".jpg")) {
			if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("skipping url: " + currentUrl);
			}
			return true;
		}
		if (outputResults.indexOf(currentUrl) === -1) {

			if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("pushing url: " + currentUrl);
			}
			outputResults.push(currentUrl.toLowerCase());
		}
	});

	return outputResults;
}
exports.replacePlainLinks = function (markdown, callback, options) {
	if (!markdown) {
		callback(markdown);
		return;
	}

	var decodedMarkdown = he.decode(markdown);
	var urls = getUrlsAndFilter(decodedMarkdown, options)
		, lookupPromises = filterValidUrlsAndLookupTitles(urls, decodedMarkdown, options)
		;

	Promise.all(lookupPromises)
		.then(function (res) {
			if (options.d && options.d.indexOf && options.d.indexOf("promisecomplete") !== -1) {
				console.log(res);
			}
			executeReplacePlainLinksWithTitles(res, decodedMarkdown, callback, options);
		});

};<commit_msg>Fixed an empty option bug.<commit_after>'use strict';

var getUrls = require('get-urls')
  , iterate = require('array-iterate')
  , isUrl = require('is-url')
  , getTitleAtUrl = require('get-title-at-url')
  , Promise = require("Promise")
  , parseDomain = require("parse-domain")
	, he = require('he')
  , exports = module.exports = {}
  ;

function replaceLinkAtPosition(markdown, urlTitleMarkdownPair, position) {
	 var linkToReplace = urlTitleMarkdownPair.url
	   , newLinkMarkdown = urlTitleMarkdownPair.titleMarkdown
		 , linkLength = linkToReplace.length
	   , prefix = markdown.substring(0, position)
	   , afterStartPosition = position + linkLength
	   , postfix = markdown.substring(afterStartPosition, markdown.length)
	   , result = prefix + newLinkMarkdown + postfix
	   ;

	 return result;
}

function generateTitleMarkdown(url, title, source) {
	return '"[' + title + '](' + url + ')", *' + source + '*';
}

function geneneratePromiseForTitleLookup(url, options) {
	if (options.d && options.d.indexOf && options.d.indexOf("urlresponse") !== -1) {
		console.log("Creating promise to find url: " + url);
	}

	 return new Promise(function (resolve, reject) {

		getTitleAtUrl(url, function (title) {
			if (options.d && options.d.indexOf && options.d.indexOf("urlresponse") !== -1) {
				console.log("title: " + title + " found for url: " + url);
			}

			if (!title) {
				resolve();
				return;
			}

			var domainData = parseDomain(url)
				, source = domainData.domain + "." + domainData.tld
				, urlTitleMarkdownPair = {
					url: url,
					titleMarkdown: generateTitleMarkdown(url, title, source)
				};

			resolve(urlTitleMarkdownPair);
		});
	});

}

function filterValidUrlsAndLookupTitles(urls, markdown, options) {
	var titleLookupPromises = []
		, relookupFailure = false
		, loweredMarkdown = markdown.toLowerCase()
		;

	iterate(urls, function (currentUrl) {
		var urlIndexTracker = 0;

		if (options.d) {
			var nextIndexOf = loweredMarkdown.indexOf(currentUrl, urlIndexTracker);
			if (nextIndexOf === -1) {
				console.log("\nCould not find url: " + currentUrl + " that was previously found in the markdown during parsing, urlIndexTracker: " + urlIndexTracker);
			}
		}
		while (loweredMarkdown.indexOf(currentUrl, urlIndexTracker) !== -1) {
			var currentUrlIndex = urlIndexTracker;
			var currentUrlStart = loweredMarkdown.indexOf(currentUrl, currentUrlIndex);
			urlIndexTracker = currentUrlStart + currentUrl.length;

			if ((currentUrlStart + currentUrl.length) < markdown.length) {
				var endOfPlusOneUrl = currentUrlStart + currentUrl.length + 1;
				var urlPlusOneChar = markdown.substring(currentUrlStart, endOfPlusOneUrl);

				if (!urlPlusOneChar.endsWith(")") && isUrl(urlPlusOneChar)) {
					continue;
				}

				if (currentUrlStart > 2) {
					var positionTwoCharactersBack = currentUrlStart - 2;
					var precendingString = markdown.substring(positionTwoCharactersBack, currentUrlStart);

					if (precendingString === "](") {
						continue;
					}
				}

				if (currentUrlStart > 3) {
					var precendingString = markdown.substring(currentUrlStart - 3, currentUrlStart);

					if (precendingString === "]: ") {
						continue;
					}
				}
			}

			var titleLookupPromise = geneneratePromiseForTitleLookup(currentUrl, options);

			titleLookupPromises.push(titleLookupPromise);
			break;
		}
	});

	return titleLookupPromises;
}
function executeReplacePlainLinksWithTitles(urlTitleList, markdown, callback, options) {
	iterate(urlTitleList, function (urlTitleMarkdownPair) {
		if (!urlTitleMarkdownPair) {
			return true;
		}

		var currentUrl = urlTitleMarkdownPair.url
			, urlIndexTracker = 0
			;

		if (!currentUrl) {
			return true;
		}

		if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
			console.log("Attempting to replace url: " + currentUrl);
			var nextIndexOf = markdown.indexOf(currentUrl, urlIndexTracker);
			console.log("Next index of url: " + currentUrl + " is at position: " + nextIndexOf + " during replacement");
		}
		while (markdown.indexOf(currentUrl, urlIndexTracker) !== -1) {
			var currentUrlIndex = urlIndexTracker;
			var currentUrlStart = markdown.indexOf(currentUrl, currentUrlIndex);
			if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
				console.log("Looking for url during replacement: " + currentUrl + " after position: " + currentUrlIndex);
			}
			if ((currentUrlStart + currentUrl.length) < markdown.length) {
				var endOfPlusOneUrl = currentUrlStart + currentUrl.length + 1;
				var urlPlusOneChar = markdown.substring(currentUrlStart, endOfPlusOneUrl);

				if (!urlPlusOneChar.endsWith(")") && isUrl(urlPlusOneChar)) {
					urlIndexTracker = currentUrlStart + currentUrl.length;
					if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
						console.log("Skipping replacement of url: " + currentUrl + " for short version of url.");
					}
					continue;
				}

				if (currentUrlStart > 2) {
					var precendingString = markdown.substring(currentUrlStart - 2, currentUrlStart);

					if (precendingString === "](") {
						urlIndexTracker = currentUrlStart + currentUrl.length;
						if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
							console.log("Skipping replacement of url: " + currentUrl + "  for ]( prefix.");
						}
						continue;
					}
				}

				if (currentUrlStart > 3) {
					var precendingString = markdown.substring(currentUrlStart - 3, currentUrlStart);

					if (precendingString === "]: ") {
						urlIndexTracker = currentUrlStart + currentUrl.length;
						if (options.d && options.d.indexOf && options.d.indexOf("replacement") !== -1) {
							console.log("Skipping replacement of url: " + currentUrl + " for ]: prefix.");
						}
						continue;
					}
				}
			}
			markdown = replaceLinkAtPosition(markdown, urlTitleMarkdownPair, currentUrlStart);
			urlIndexTracker = currentUrlStart + urlTitleMarkdownPair.titleMarkdown.length;
		}
	});

	callback(markdown);
}

function getUrlsAndFilter(markdown, options) {
	var urls = getUrls(markdown, { stripWWW: false, stripFragment: false, normalizeProtocol: false, removeQueryParameters: [] })
		, outputResults = []
		;

	if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("getUrls output:");
				console.log(urls);
	}
	iterate(urls, function (currentUrl) {
		if (!currentUrl) {
			return true;
		}

		if (currentUrl.endsWith(")") && currentUrl.indexOf("(") === -1) {
			currentUrl = currentUrl.substring(0, currentUrl.length - 1);
			if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("new url: " + currentUrl);
			}
		}

		if (currentUrl.toLowerCase().endsWith(".jpg")) {
			if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("skipping url: " + currentUrl);
			}
			return true;
		}
		if (outputResults.indexOf(currentUrl) === -1) {

			if (options.d && options.d.indexOf && options.d.indexOf("urlread") !== -1) {
				console.log("pushing url: " + currentUrl);
			}
			outputResults.push(currentUrl.toLowerCase());
		}
	});

	return outputResults;
}
exports.replacePlainLinks = function (markdown, callback, options) {
	if (!markdown) {
		callback(markdown);
		return;
	}

	if(!options){
		options = {};
	}

	var decodedMarkdown = he.decode(markdown);
	var urls = getUrlsAndFilter(decodedMarkdown, options)
		, lookupPromises = filterValidUrlsAndLookupTitles(urls, decodedMarkdown, options)
		;

	Promise.all(lookupPromises)
		.then(function (res) {
			if (options.d && options.d.indexOf && options.d.indexOf("promisecomplete") !== -1) {
				console.log(res);
			}
			executeReplacePlainLinksWithTitles(res, decodedMarkdown, callback, options);
		});

};<|endoftext|>


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

const
    CONNECT_THROTTLE=5 // ms per connection
    , UPDATE_TIMEOUT = 30000 // in ms
    , OPEN_SEMAPHORE = 100;

var program = require('commander'),
    Server = require('./lib/Server'),
    Client = require('./lib/Client'),
    EndPoint = require('./lib/EndPoint'),
    debug = require('debug'),
    uuid = require('node-uuid'),
    moment = require('moment'),
    webserver = require('./webserver');

program
    .version('0.0.1a')
    .option('-s, --pushgoserver <server>', 'Push go server url, e.g., pushserver.test.com', 'push.services.mozilla.com')
    .option('-c, --clients <clients>', 'Number of client connections', Number, 1)
    .option('-C, --channels <channels>', 'Number of channels per client', Number, 1)
    .option('-u, --minupdatetime <minupdatetime>', 'Minimum milliseconds between version updates', Number, 500)
    .option('-U, --maxupdatetime <minpingtime>', 'Maximum milliseconds between version updates', Number, 1000)
    .option('-S, --ssl', "Use https")
    .parse(process.argv);

var testy = debug('testy');
var deep = debug('deep');

var DOUT = (typeof(process.env.NODEBUG) == 'undefined');

var startTime = moment();

if (program.ssl) {
    var http = require('https');
} else {
    var http = require('http');
}

http.localAgent = new http.Agent({rejectUnauthorized: false});

/**
 * This dirty little blob just gets updated
 * as the test runs and sent to the UI via websockets...
 */
var stats = {
    // this should match web/public/static/js/model/Stats.js to make
    // easier to send data to the backbone Model
    test_seconds: 0

    , server        : program.pushgoserver
    , minupdatetime : program.minupdatetime
    , maxupdatetime : program.maxupdatetime
    , clients       : program.clients
    , channels      : program.channels
    , timeout_time  : UPDATE_TIMEOUT

    // Connection Stats
    , conn_current   : 0
    , conn_attempted : 0
    , conn_ok        : 0
    , conn_drop      : 0

    // Connection Times
    , c_count  : 0
    , c_t5s    : 0
    , c_t30s   : 0
    , c_t60s   : 0
    , c_t300s  : 0
    , c_t600s  : 0
    , c_t1800s : 0
    , c_tXs    : 0

    // Update Stats
    , put_sent           : 0
    , put_failed         : 0
    , update_outstanding : 0
    , update_received    : 0
    , update_timeout     : 0
    , update_invalid     : 0
    , update_net_error   : 0
    , update_err_empty   : 0 // special, server sent an empty notify packet

    // update timing latency (PushServer -> WS -> Client)
    , u_count    : 0
    , u_t50ms    : 0
    , u_t100ms   : 0
    , u_t500ms   : 0
    , u_t1500ms  : 0
    , u_t5000ms  : 0
    , u_t10000ms : 0
    , u_t20000ms : 0
    , u_t60000ms : 0
    , u_tXms     : 0

    // Misc
    , skip_timeout : 0
};

function random( min, max ) {
    return Math.random() * ( max - min ) + min;
}

var updateTimes = [50, 100, 500, 1500, 5000, 10000, 20000, 60000];
function resultHandler(result) {
    if (DOUT) deep("*** RESULT: (%dms) %s | %s ***",
        result.time, result.status, result.endpoint.channelID
    )

    switch (result.status) {
        case 'GOT_VERSION_OK':
            stats.update_outstanding -= 1;
            stats.update_received += 1;

            var checkTime;
            var counted = false;
            stats.u_count += 1;

            for (var i=0; i < updateTimes.length; i++) {
                checkTime = updateTimes[i];
                if (result.data <= checkTime) {
                    stats["u_t"+checkTime + "ms"] += 1;
                    counted = true;
                    break;
                }
            }

            if (counted === false) {
                stats.u_tXms += 1;
            }

            if (DOUT) testy("\\o/. ws lag: %dms", result.data);
            break;

        case 'ERR_VER_INVALID':
            stats.update_outstanding -= 1;
            stats.update_invalid += 1;
            if (DOUT) testy("ERROR: Unexpected Version. Got %d, expected: %d",
                    result.data.got,
                    result.data.expected
                );
            break;

        case 'SKIP_TIMEOUT_CREATE':
            stats.skip_timeout += 1;
            break;

    }

    /*
    if (result.status != "PUT_OK" && result.endpoint.client.connected !== false) {
        var nextUpdate = Math.floor(random(program.minupdatetime, program.maxupdatetime));
        if (DOUT) testy("Waiting %dms to send another update", nextUpdate)
        setTimeout(
            result.endpoint.sendNextVersion.bind(result.endpoint, UPDATE_TIMEOUT),
            nextUpdate
        );
    }
    */
}

var connectionTimes = [5, 30, 60, 300, 600, 1800];
function handleClientClose(timeConnected) {
    stats.conn_current -= 1;
    stats.conn_drop += 1;
    var recorded = false;

    stats.c_count += 1;

    for (var i=0; i<connectionTimes.length; i++) {
        var s = Math.round(timeConnected/1000);
        if (s <= connectionTimes[i]) {
            stats['c_t' + connectionTimes[i] + 's'] += 1;
            recorded = true;
            break;
        }
    }

    if (recorded === false) {
        stats.c_tXs += 1;
    }

    // make sure we try to maintain program.clients target
    createClient();
}

function handleClientEmptyNotify() {
    stats.update_err_empty += 1;
}

var clientCount = 0;

// ghetto async creation semaphore..
var opening = OPEN_SEMAPHORE;

function handleClientOpen() {
    stats.conn_current += 1;
    stats.conn_ok += 1;

    opening++;
}

function handleNewEndpoint(endpoint) {
    if (DOUT) testy("New Endpoint: %s", endpoint.channelID);
    endpoint.on('result', resultHandler);
    //endpoint.sendNextVersion();
}

function handleClientRegistered(client) {
    server.addClient(client);
}

function createClient() {
    clientCount += 1;
    if (DOUT) testy("Creating client: %d", clientCount);

    opening--;
    var c = new Client(program.pushgoserver, program.ssl ? 'wss://' : 'ws://', http);

    for(var j = 0; j < program.channels; j++) {
        c.registerChannel(uuid.v1());
    }

    c.on('newendpoint', handleNewEndpoint);

    c.once('open', handleClientOpen);
    c.once('close', handleClientClose);
    c.once('registered', handleClientRegistered);

    c.on('err_notification_empty', handleClientEmptyNotify);

    stats.conn_attempted += 1;
    c.start();
}

/**
 * SERVER - this controls sending out of requests
 */
var server = new Server(http, program.minupdatetime, program.maxupdatetime, 2000);
server.start();

server.on('PUT_FAIL', function(channelID, statusCode, body) {
    stats.put_sent += 1;
    stats.put_failed += 1;

    if (DOUT) testy("PUT_FAIL %s. HTTP %s %s",
            channelID,
            statusCode,
            body
        );
});

server.on('PUT_OK', function(channelID) {
    stats.put_sent += 1;
    stats.update_outstanding += 1;
    if (DOUT) testy("PUT_OK %s", channelID);
});

server.on('ERR_NETWORK', function(err) {
    stats.update_net_error += 1;
    if (DOUT) testy('Network Error: %s', result.data);
});

server.on('TIMEOUT', function(channelID, timeoutTime) {
    stats.update_outstanding -= 1;
    stats.update_timeout += 1;
    if (DOUT) testy('TIMEOUT, expired: %dms', result.data);
});

/**
 * Let's start creating Clients!
 */
setTimeout(function ensureEnoughClients() {
    if(stats.conn_current + (OPEN_SEMAPHORE - opening) >= program.clients) {
        setTimeout(ensureEnoughClients, CONNECT_THROTTLE * 10);
        return;
    }

    if (opening <= 0) {
        setTimeout(ensureEnoughClients, CONNECT_THROTTLE);
        return;
    }

    createClient();
    setTimeout(ensureEnoughClients, CONNECT_THROTTLE);
}, 100);

webserver.startup(function(err, server) {
    debug('webserver')("Webserver listening on " + server.address().port);

    setInterval(function() {
        stats.test_seconds = Math.floor(moment().diff(startTime)/1000);
        server.emit('stats', stats);
    }, 1000);
});
<commit_msg>remove old sending code<commit_after>#!/usr/bin/env node

const
    CONNECT_THROTTLE=5 // ms per connection
    , UPDATE_TIMEOUT = 30000 // in ms
    , OPEN_SEMAPHORE = 100;

var program = require('commander'),
    Server = require('./lib/Server'),
    Client = require('./lib/Client'),
    EndPoint = require('./lib/EndPoint'),
    debug = require('debug'),
    uuid = require('node-uuid'),
    moment = require('moment'),
    webserver = require('./webserver');

program
    .version('0.0.1a')
    .option('-s, --pushgoserver <server>', 'Push go server url, e.g., pushserver.test.com', 'push.services.mozilla.com')
    .option('-c, --clients <clients>', 'Number of client connections', Number, 1)
    .option('-C, --channels <channels>', 'Number of channels per client', Number, 1)
    .option('-u, --minupdatetime <minupdatetime>', 'Minimum milliseconds between version updates', Number, 500)
    .option('-U, --maxupdatetime <minpingtime>', 'Maximum milliseconds between version updates', Number, 1000)
    .option('-S, --ssl', "Use https")
    .parse(process.argv);

var testy = debug('testy');
var deep = debug('deep');

var DOUT = (typeof(process.env.NODEBUG) == 'undefined');

var startTime = moment();

if (program.ssl) {
    var http = require('https');
} else {
    var http = require('http');
}

http.localAgent = new http.Agent({rejectUnauthorized: false});

/**
 * This dirty little blob just gets updated
 * as the test runs and sent to the UI via websockets...
 */
var stats = {
    // this should match web/public/static/js/model/Stats.js to make
    // easier to send data to the backbone Model
    test_seconds: 0

    , server        : program.pushgoserver
    , minupdatetime : program.minupdatetime
    , maxupdatetime : program.maxupdatetime
    , clients       : program.clients
    , channels      : program.channels
    , timeout_time  : UPDATE_TIMEOUT

    // Connection Stats
    , conn_current   : 0
    , conn_attempted : 0
    , conn_ok        : 0
    , conn_drop      : 0

    // Connection Times
    , c_count  : 0
    , c_t5s    : 0
    , c_t30s   : 0
    , c_t60s   : 0
    , c_t300s  : 0
    , c_t600s  : 0
    , c_t1800s : 0
    , c_tXs    : 0

    // Update Stats
    , put_sent           : 0
    , put_failed         : 0
    , update_outstanding : 0
    , update_received    : 0
    , update_timeout     : 0
    , update_invalid     : 0
    , update_net_error   : 0
    , update_err_empty   : 0 // special, server sent an empty notify packet

    // update timing latency (PushServer -> WS -> Client)
    , u_count    : 0
    , u_t50ms    : 0
    , u_t100ms   : 0
    , u_t500ms   : 0
    , u_t1500ms  : 0
    , u_t5000ms  : 0
    , u_t10000ms : 0
    , u_t20000ms : 0
    , u_t60000ms : 0
    , u_tXms     : 0

    // Misc
    , skip_timeout : 0
};

function random( min, max ) {
    return Math.random() * ( max - min ) + min;
}

var updateTimes = [50, 100, 500, 1500, 5000, 10000, 20000, 60000];
function resultHandler(result) {
    if (DOUT) deep("*** RESULT: (%dms) %s | %s ***",
        result.time, result.status, result.endpoint.channelID
    )

    switch (result.status) {
        case 'GOT_VERSION_OK':
            stats.update_outstanding -= 1;
            stats.update_received += 1;

            var checkTime;
            var counted = false;
            stats.u_count += 1;

            for (var i=0; i < updateTimes.length; i++) {
                checkTime = updateTimes[i];
                if (result.data <= checkTime) {
                    stats["u_t"+checkTime + "ms"] += 1;
                    counted = true;
                    break;
                }
            }

            if (counted === false) {
                stats.u_tXms += 1;
            }

            if (DOUT) testy("\\o/. ws lag: %dms", result.data);
            break;

        case 'ERR_VER_INVALID':
            stats.update_outstanding -= 1;
            stats.update_invalid += 1;
            if (DOUT) testy("ERROR: Unexpected Version. Got %d, expected: %d",
                    result.data.got,
                    result.data.expected
                );
            break;

        case 'SKIP_TIMEOUT_CREATE':
            stats.skip_timeout += 1;
            break;

    }
}

var connectionTimes = [5, 30, 60, 300, 600, 1800];
function handleClientClose(timeConnected) {
    stats.conn_current -= 1;
    stats.conn_drop += 1;
    var recorded = false;

    stats.c_count += 1;

    for (var i=0; i<connectionTimes.length; i++) {
        var s = Math.round(timeConnected/1000);
        if (s <= connectionTimes[i]) {
            stats['c_t' + connectionTimes[i] + 's'] += 1;
            recorded = true;
            break;
        }
    }

    if (recorded === false) {
        stats.c_tXs += 1;
    }

    // make sure we try to maintain program.clients target
    createClient();
}

function handleClientEmptyNotify() {
    stats.update_err_empty += 1;
}

var clientCount = 0;

// ghetto async creation semaphore..
var opening = OPEN_SEMAPHORE;

function handleClientOpen() {
    stats.conn_current += 1;
    stats.conn_ok += 1;

    opening++;
}

function handleNewEndpoint(endpoint) {
    if (DOUT) testy("New Endpoint: %s", endpoint.channelID);
    endpoint.on('result', resultHandler);
    //endpoint.sendNextVersion();
}

function handleClientRegistered(client) {
    server.addClient(client);
}

function createClient() {
    clientCount += 1;
    if (DOUT) testy("Creating client: %d", clientCount);

    opening--;
    var c = new Client(program.pushgoserver, program.ssl ? 'wss://' : 'ws://', http);

    for(var j = 0; j < program.channels; j++) {
        c.registerChannel(uuid.v1());
    }

    c.on('newendpoint', handleNewEndpoint);

    c.once('open', handleClientOpen);
    c.once('close', handleClientClose);
    c.once('registered', handleClientRegistered);

    c.on('err_notification_empty', handleClientEmptyNotify);

    stats.conn_attempted += 1;
    c.start();
}

/**
 * SERVER - this controls sending out of requests
 */
var server = new Server(http, program.minupdatetime, program.maxupdatetime, 2000);
server.start();

server.on('PUT_FAIL', function(channelID, statusCode, body) {
    stats.put_sent += 1;
    stats.put_failed += 1;

    if (DOUT) testy("PUT_FAIL %s. HTTP %s %s",
            channelID,
            statusCode,
            body
        );
});

server.on('PUT_OK', function(channelID) {
    stats.put_sent += 1;
    stats.update_outstanding += 1;
    if (DOUT) testy("PUT_OK %s", channelID);
});

server.on('ERR_NETWORK', function(err) {
    stats.update_net_error += 1;
    if (DOUT) testy('Network Error: %s', result.data);
});

server.on('TIMEOUT', function(channelID, timeoutTime) {
    stats.update_outstanding -= 1;
    stats.update_timeout += 1;
    if (DOUT) testy('TIMEOUT, expired: %dms', result.data);
});

/**
 * Let's start creating Clients!
 */
setTimeout(function ensureEnoughClients() {
    if(stats.conn_current + (OPEN_SEMAPHORE - opening) >= program.clients) {
        setTimeout(ensureEnoughClients, CONNECT_THROTTLE * 10);
        return;
    }

    if (opening <= 0) {
        setTimeout(ensureEnoughClients, CONNECT_THROTTLE);
        return;
    }

    createClient();
    setTimeout(ensureEnoughClients, CONNECT_THROTTLE);
}, 100);

webserver.startup(function(err, server) {
    debug('webserver')("Webserver listening on " + server.address().port);

    setInterval(function() {
        stats.test_seconds = Math.floor(moment().diff(startTime)/1000);
        server.emit('stats', stats);
    }, 1000);
});
<|endoftext|>


<commit_before>/*
 * @class LayerChooser
 * @aka window.LayerChooser
 * @inherits L.Controls.Layers
 *
 * Provides 'persistence' of layers display state between sessions.
 *
 * Also some additional methods provided, see below.
 */

'use strict';

var LayerChooser = L.Control.Layers.extend({
  options: {
    // @option sortLayers: Boolean = true
    // Ensures stable sort order (based on initial), while still providing ability
    // to enforce specific order with `addBaseLayer`/`addOverlay`
    // `sortPriority` option.
    sortLayers: true,

    sortFunction: function (layerA, layerB) {
      var a = layerA._sortPriority;
      var b = layerB._sortPriority;
      return a < b ? -1 : (b < a ? 1 : 0);
    }
  },

  initialize: function (baseLayers, overlays, options) {
    this._overlayStatus = {};
    var layersJSON = localStorage['ingress.intelmap.layergroupdisplayed'];
    if (layersJSON) {
      try {
        this._overlayStatus = JSON.parse(layersJSON);
      } catch (e) {
        log.error(e);
      }
    }
    this._mapToAdd = options && options.map;
    this.lastBaseLayerName = localStorage['iitc-base-map'];
    this._lastPriority = -1000; // initial layers get priority <0
    L.Control.Layers.prototype.initialize.apply(this, arguments);
    this._lastPriority = 0; // any following gets >0
  },

  _addLayer: function (layer, label, overlay, options) {
    options = options || {};
    // stored on first add (with .addBaseLayer/.addOverlay)
    if (!layer._name) {
      // name should be unique, otherwise behavior of other methods is undefined (typically: first found will be taken)
      layer._name = label;
      layer._overlay = overlay;
      layer._persistent = 'persistent' in options ? options.persistent : true;
    } else {
      label = label || layer._label || layer._name;
    }
    L.Control.Layers.prototype._addLayer.call(this, layer, label, overlay);
    // provide stable sort order
    if ('sortPriority' in options) {
      layer._sortPriority = options.sortPriority;
    } else if (!('_sortPriority' in layer)) {
      this._lastPriority = this._lastPriority + 10;
      layer._sortPriority = this._lastPriority;
    }
    if (layer._overlay) {
      layer._default = 'default' in options ? options.default : true;
    }
    var map = this._map || this._mapToAdd;
    if (!layer._persistent) {
      if ('enable' in options ? options.enable : layer._default) {
        layer.addTo(map);
      }
      return;
    }
    if (overlay) {
      layer._statusTracking = function (e) {
        this._storeOverlayState(layer._name, e.type === 'add');
      };
      layer.on('add remove', layer._statusTracking, this);
      if ('enable' in options) { // do as explicitly specified
        map[options.enable ? 'addLayer' : 'removeLayer'](layer);
      } else if (layer._map) { // already on map, only store state
        this._storeOverlayState(layer._name, true);
      } else { // restore at recorded state
        if (this._isOverlayDisplayed(layer._name, layer._default)) {
          layer.addTo(map);
        }
      }
    } else {
      layer._statusTracking = function () {
        localStorage['iitc-base-map'] = layer._name;
      };
      layer.on('add', layer._statusTracking);
    }
  },

  // @miniclass LayersEntry options (LayerChooser)
  // @aka layersEntry options

  // @option persistent: Boolean = true
  // * When `false` - baselayer's status is not tracked.

  // @option sortPriority: Number = *
  // Enforces specific order in control, lower value means layer's upper position.
  // If not specified - the value will be assigned implicitly in increasing manner.

  // @method addBaseLayer(layer: Layer, name: String, options?: LayersEntry options): this
  // Adds a base layer (radio button entry) with the given name to the control.
  addBaseLayer: function (layer, name, options) {
    this._addLayer(layer, name, false, options);
    return (this._map) ? this._update() : this;
  },

  // @miniclass AddOverlay options (LayerChooser)
  // @aka addOverlay options
  // @inherits LayersEntry options

  // @option persistent: Boolean = true
  // * When `true` (or not specified) - adds overlay to the map as well,
  //   if it's last state was active.
  //   If no record exists then value specified in `default` option is used.
  // * When `false` - overlay status is not tracked, `default` option is honored too.

  // @option default: Boolean = true
  // Default state of overlay (used only when no record about previous state found).

  // @option enable: Boolean
  // Enforce specified state ignoring previously saved.

  // @method addOverlay(layer: L.Layer, name: String, options?: AddOverlay options): this
  // Adds an overlay (checkbox entry) with the given name to the control.
  addOverlay: function (layer, name, options) {
    this._addLayer(layer, name, true, options);
    return (this._map) ? this._update() : this;
  },

  // @method removeLayer(layer: Layer|String, keepOnMap?: Boolean): this
  // Removes the given layer from the control.
  // Either layer object or it's name in the control must be specified.
  // Layer is removed from the map as well, except `keepOnMap` argument is true. // todo?? {keepOnMap: true}
  removeLayer: function (layer, keepOnMap) {
    layer = this.getLayer(layer);
    if (layer) {
      if (layer._statusTracking) {
        layer.off('add remove', layer._statusTracking, this);
        delete layer._statusTracking;
      }
      L.Control.Layers.prototype.removeLayer.apply(this, arguments);
      if (this._map && !keepOnMap) {
        map.removeLayer(layer);
      }
    } else {
      log.warn('Layer not found');
    }
    return this;
  },

  _storeOverlayState: function (name, isDisplayed) {
    this._overlayStatus[name] = isDisplayed;
    localStorage['ingress.intelmap.layergroupdisplayed'] = JSON.stringify(this._overlayStatus);
  },

  _isOverlayDisplayed: function (name, defaultState) {
    if (name in this._overlayStatus) {
      return this._overlayStatus[name];
    }
    return defaultState;
  },

  __byName: function (el) {
    var name = this.toString();
    return el.layer._name === name ||
      el.name === name;
  },

  __byLayer: function (el) {
    return el.layer === this;
  },

  // layer: either Layer or it's name in the control
  _layerInfo: function (layer) {
    var fn = layer instanceof L.Layer
      ? this.__byLayer
      : this.__byName;
    return this._layers.find(fn, layer);
  },

  // @method getLayer(name: String|Layer): Layer
  // Returns layer by it's name in the control, or by layer object itself.
  // The latter can be used to ensure the layer is in layerChooser.
  getLayer: function (layer) {
    var info = this._layerInfo(layer);
    return info && info.layer;
  },

  // @method showLayer(layer: Layer|String|Number, display?: Boolean): this
  // Switches layer's display state to given value (true by default).
  // Layer can be specified also by it's name in the control.
  showLayer: function (layer, display) {
    var info = this._layers[layer]; // layer is index, private use only
    if (info) {
      layer = info.layer;
    } else {
      layer = this.getLayer(layer);
      if (!layer) {
        log.warn('Layer not found');
        return this;
      }
    }
    var map = this._map;
    if (display || arguments.length === 1) {
      if (!map.hasLayer(layer)) {
        if (!layer._overlay) {
          // if it's a base layer, remove any others
          this._layers.forEach(function (el) {
            if (!el.overlay && el.layer !== layer) {
              map.removeLayer(el.layer);
            }
          });
        }
        map.addLayer(layer);
      }
    } else {
      map.removeLayer(layer);
    }
    return this;
  },

  // @method setLabel(layer: String|Layer, label?: String): this
  // Sets layers label to specified label text (html),
  // or resets it to original name when label is not specified.
  setLabel: function (layer, label) {
    var fn = layer instanceof L.Layer
      ? this.__byLayer
      : this.__byName;
    var idx = this._layers.findIndex(fn, layer);
    if (idx === -1) {
      log.warn('Layer not found');
      return this;
    }
    var info = this._layers[idx];
    label = label || info.layer._name;
    info.name = label;
    var nameEl = this._layerControlInputs[idx].closest('label').querySelector('span');
    nameEl.innerHTML = ' ' + label;
    return this;
  },

  // adds listeners to the overlays list to make inputs toggleable.
  _initLayout: function () {
    L.Control.Layers.prototype._initLayout.call(this);
    $(this._overlaysList).on('click taphold', 'label', function (e) {
      if (!(e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.type === 'taphold')) {
        return;
      }
      // e.preventDefault(); // seems no effect
      var input = e.target.closest('label').querySelector('input');
      var idx = this._layerControlInputs.indexOf(input);
      this._toggleOverlay(idx);
    }.bind(this));
  },

  _filterOverlays: function (el) {
    return el.overlay &&
      ['DEBUG Data Tiles', 'Resistance', 'Enlightened'].indexOf(el.layer._name) === -1;
  },

  // Hides all the control's overlays except given one,
  // or restores all, if it was the only one displayed (or none was displayed).
  _toggleOverlay: function (idx) {
    var info = this._layers[idx];
    if (!info || !info.overlay) {
      log.warn('Overlay not found: ', info);
      return;
    }
    var map = this._map;

    var isChecked = map.hasLayer(info.layer);
    var checked = 0;
    var overlays = this._layers.filter(this._filterOverlays);
    overlays.forEach(function (el) {
      if (map.hasLayer(el.layer)) { checked++; }
    });

    if (checked === 0 || isChecked && checked === 1) {
      // if nothing is selected, or specified overlay is exclusive,
      // assume all boxes should be checked again
      overlays.forEach(function (el) {
        if (el.layer._default) {
          map.addLayer(el.layer);
        }
      });
    } else {
      // uncheck all, check specified
      overlays.forEach(function (el) {
        if (el.layer === info.layer) {
          map.addLayer(el.layer);
        } else {
          map.removeLayer(el.layer);
        }
      });
    }
  },

  _stripHtmlTags: function (str) {
    return str.replace(/(<([^>]+)>)/gi, ''); // https://css-tricks.com/snippets/javascript/strip-html-tags-in-javascript/
  },

  // !!deprecated
  getLayers: function () {
    var baseLayers = [];
    var overlayLayers = [];
    this._layers.forEach(function (info, idx) {
      (info.overlay ? overlayLayers : baseLayers).push({
        layerId: idx,
        name: this._stripHtmlTags(info.name), // IITCm does not support html in layers labels
        active: this._map.hasLayer(info.layer)
      });
    }, this);

    return {
      baseLayers: baseLayers,
      overlayLayers: overlayLayers
    };
  }
});

window.LayerChooser = LayerChooser;

function debounce (callback, time) { // https://gist.github.com/nmsdvid/8807205#gistcomment-2641356
  var timeout;
  return function () {
    var context = this;
    var args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(function () {
      timeout = null;
      callback.apply(context, args);
    }, time);
  };
}

if (typeof android !== 'undefined' && android && android.setLayers) {
  // hook some additional code into the LayerControl so it's easy for the mobile app to interface with it
  LayerChooser.include({
    _setAndroidLayers: debounce(function () { // update layer menu in IITCm
      var l = this.getLayers();
      android.setLayers(JSON.stringify(l.baseLayers), JSON.stringify(l.overlayLayers));
    }, 1000),

    setLabel: (function (setLabel) {
      return function () {
        this._setAndroidLayers();
        return setLabel.apply(this, arguments);
      };
    })(LayerChooser.prototype.setLabel),

    _update: function () {
      this._setAndroidLayers();
      return L.Control.Layers.prototype._update.apply(this, arguments);
    }
  });
}

// contains current status(on/off) of overlay layerGroups.
// !!deprecated: use `map.hasLayer` instead (https://leafletjs.com/reference.html#map-haslayer)
window.overlayStatus = {}; // to be set in constructor

// Reads recorded layerGroup status (as it may not be added to map yet),
// return `defaultDisplay` if no record found.
// !!deprecated: for most use cases prefer `getLayer()` method
// or `map.hasLayer` (https://leafletjs.com/reference.html#map-haslayer)
// window.isLayerGroupDisplayed = function (name, defaultDisplay) { // ...
window.isLayerGroupDisplayed = L.Util.falseFn; // to be set in constructor

LayerChooser.addInitHook(function () {
  window.overlayStatus = this._overlayStatus;
  window.isLayerGroupDisplayed = this._isOverlayDisplayed.bind(this);
});

// !!deprecated: use `layerChooser.addOverlay` directly
window.addLayerGroup = function (name, layerGroup, defaultDisplay) {
  var options = {default: defaultDisplay};
  if (arguments.length < 3) { options = undefined; }
  window.layerChooser.addOverlay(layerGroup, name, options);
};

// !!deprecated: use `layerChooser.removeLayer` directly
// our method differs from inherited (https://leafletjs.com/reference.html#control-layers-removelayer),
// as (by default) layer is removed from the map as well, see description for more details.
window.removeLayerGroup = function (layerGroup) {
  window.layerChooser.removeLayer(layerGroup);
};
<commit_msg>layerchooser.js: stop polluting of layer object with numerous layerChooser's private properties<commit_after>/*
 * @class LayerChooser
 * @aka window.LayerChooser
 * @inherits L.Controls.Layers
 *
 * Provides 'persistence' of layers display state between sessions.
 *
 * Also some additional methods provided, see below.
 */

'use strict';

var LayerChooser = L.Control.Layers.extend({
  options: {
    // @option sortLayers: Boolean = true
    // Ensures stable sort order (based on initial), while still providing ability
    // to enforce specific order with `addBaseLayer`/`addOverlay`
    // `sortPriority` option.
    sortLayers: true,

    sortFunction: function (layerA, layerB) {
      var a = layerA._chooser.sortPriority;
      var b = layerB._chooser.sortPriority;
      return a < b ? -1 : (b < a ? 1 : 0);
    }
  },

  initialize: function (baseLayers, overlays, options) {
    this._overlayStatus = {};
    var layersJSON = localStorage['ingress.intelmap.layergroupdisplayed'];
    if (layersJSON) {
      try {
        this._overlayStatus = JSON.parse(layersJSON);
      } catch (e) {
        log.error(e);
      }
    }
    this._mapToAdd = options && options.map;
    this.lastBaseLayerName = localStorage['iitc-base-map'];
    this._lastPriority = -1000; // initial layers get priority <0
    L.Control.Layers.prototype.initialize.apply(this, arguments);
    this._lastPriority = 0; // any following gets >0
  },

  _addLayer: function (layer, label, overlay, options) {
    options = options || {};
    // stored on first add (with .addBaseLayer/.addOverlay)
    var data = layer._chooser;
    if (!data) {
      data = {
        // name should be unique, otherwise behavior of other methods is undefined
        // (typically: first found will be taken)
        name: label,
        overlay: overlay,
        persistent: 'persistent' in options ? options.persistent : true
      };
      layer._chooser = data;
    } else {
      label = label || data.label || data.name;
    }
    // provide stable sort order
    if ('sortPriority' in options) {
      data.sortPriority = options.sortPriority;
    } else if (!('sortPriority' in data)) {
      this._lastPriority = this._lastPriority + 10;
      data.sortPriority = this._lastPriority;
    }
    L.Control.Layers.prototype._addLayer.call(this, layer, label, overlay);
    if (data.overlay) {
      data.default = 'default' in options ? options.default : true;
    }
    var map = this._map || this._mapToAdd;
    if (!data.persistent) {
      if (!data.overlay) { return; }
      if ('enable' in options ? options.enable : data.default) {
        layer.addTo(map);
      }
      return;
    }
    if (overlay) {
      data.statusTracking = function (e) {
        this._storeOverlayState(data.name, e.type === 'add');
      };
      layer.on('add remove', data.statusTracking, this);
      if ('enable' in options) { // do as explicitly specified
        map[options.enable ? 'addLayer' : 'removeLayer'](layer);
      } else if (layer._map) { // already on map, only store state
        this._storeOverlayState(data.name, true);
      } else { // restore at recorded state
        if (this._isOverlayDisplayed(data.name, data.default)) {
          layer.addTo(map);
        }
      }
    } else {
      data.statusTracking = function () {
        localStorage['iitc-base-map'] = data.name;
      };
      layer.on('add', data.statusTracking);
    }
  },

  // @miniclass LayersEntry options (LayerChooser)
  // @aka layersEntry options

  // @option persistent: Boolean = true
  // * When `false` - baselayer's status is not tracked.

  // @option sortPriority: Number = *
  // Enforces specific order in control, lower value means layer's upper position.
  // If not specified - the value will be assigned implicitly in increasing manner.

  // @method addBaseLayer(layer: Layer, name: String, options?: LayersEntry options): this
  // Adds a base layer (radio button entry) with the given name to the control.
  addBaseLayer: function (layer, name, options) {
    this._addLayer(layer, name, false, options);
    return (this._map) ? this._update() : this;
  },

  // @miniclass AddOverlay options (LayerChooser)
  // @aka addOverlay options
  // @inherits LayersEntry options

  // @option persistent: Boolean = true
  // * When `true` (or not specified) - adds overlay to the map as well,
  //   if it's last state was active.
  //   If no record exists then value specified in `default` option is used.
  // * When `false` - overlay status is not tracked, `default` option is honored too.

  // @option default: Boolean = true
  // Default state of overlay (used only when no record about previous state found).

  // @option enable: Boolean
  // Enforce specified state ignoring previously saved.

  // @method addOverlay(layer: L.Layer, name: String, options?: AddOverlay options): this
  // Adds an overlay (checkbox entry) with the given name to the control.
  addOverlay: function (layer, name, options) {
    this._addLayer(layer, name, true, options);
    return (this._map) ? this._update() : this;
  },

  // @method removeLayer(layer: Layer|String, keepOnMap?: Boolean): this
  // Removes the given layer from the control.
  // Either layer object or it's name in the control must be specified.
  // Layer is removed from the map as well, except `keepOnMap` argument is true. // todo?? {keepOnMap: true}
  removeLayer: function (layer, keepOnMap) {
    layer = this.getLayer(layer);
    if (layer) {
      var data = layer._chooser;
      if (data.statusTracking) {
        layer.off('add remove', data.statusTracking, this);
        delete data.statusTracking;
      }
      L.Control.Layers.prototype.removeLayer.apply(this, arguments);
      if (this._map && !keepOnMap) {
        map.removeLayer(layer);
      }
    } else {
      log.warn('Layer not found');
    }
    return this;
  },

  _storeOverlayState: function (name, isDisplayed) {
    this._overlayStatus[name] = isDisplayed;
    localStorage['ingress.intelmap.layergroupdisplayed'] = JSON.stringify(this._overlayStatus);
  },

  _isOverlayDisplayed: function (name, defaultState) {
    if (name in this._overlayStatus) {
      return this._overlayStatus[name];
    }
    return defaultState;
  },

  __byName: function (el) {
    var name = this.toString();
    return el.layer._chooser.name === name ||
      el.name === name;
  },

  __byLayer: function (el) {
    return el.layer === this;
  },

  // layer: either Layer or it's name in the control
  _layerInfo: function (layer) {
    var fn = layer instanceof L.Layer
      ? this.__byLayer
      : this.__byName;
    return this._layers.find(fn, layer);
  },

  // @method getLayer(name: String|Layer): Layer
  // Returns layer by it's name in the control, or by layer object itself.
  // The latter can be used to ensure the layer is in layerChooser.
  getLayer: function (layer) {
    var info = this._layerInfo(layer);
    return info && info.layer;
  },

  // @method showLayer(layer: Layer|String|Number, display?: Boolean): this
  // Switches layer's display state to given value (true by default).
  // Layer can be specified also by it's name in the control.
  showLayer: function (layer, display) {
    var info = this._layers[layer]; // layer is index, private use only
    if (info) {
      layer = info.layer;
    } else {
      layer = this.getLayer(layer);
      if (!layer) {
        log.warn('Layer not found');
        return this;
      }
    }
    var map = this._map;
    if (display || arguments.length === 1) {
      if (!map.hasLayer(layer)) {
        if (!layer._chooser.overlay) {
          // if it's a base layer, remove any others
          this._layers.forEach(function (el) {
            if (!el.overlay && el.layer !== layer) {
              map.removeLayer(el.layer);
            }
          });
        }
        map.addLayer(layer);
      }
    } else {
      map.removeLayer(layer);
    }
    return this;
  },

  // @method setLabel(layer: String|Layer, label?: String): this
  // Sets layers label to specified label text (html),
  // or resets it to original name when label is not specified.
  setLabel: function (layer, label) {
    var fn = layer instanceof L.Layer
      ? this.__byLayer
      : this.__byName;
    var idx = this._layers.findIndex(fn, layer);
    if (idx === -1) {
      log.warn('Layer not found');
      return this;
    }
    var info = this._layers[idx];
    label = label || info.layer._chooser.name;
    info.name = label;
    var nameEl = this._layerControlInputs[idx].closest('label').querySelector('span');
    nameEl.innerHTML = ' ' + label;
    return this;
  },

  // adds listeners to the overlays list to make inputs toggleable.
  _initLayout: function () {
    L.Control.Layers.prototype._initLayout.call(this);
    $(this._overlaysList).on('click taphold', 'label', function (e) {
      if (!(e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.type === 'taphold')) {
        return;
      }
      // e.preventDefault(); // seems no effect
      var input = e.target.closest('label').querySelector('input');
      var idx = this._layerControlInputs.indexOf(input);
      this._toggleOverlay(idx);
    }.bind(this));
  },

  _filterOverlays: function (el) {
    return el.overlay &&
      ['DEBUG Data Tiles', 'Resistance', 'Enlightened'].indexOf(el.layer._chooser.name) === -1;
  },

  // Hides all the control's overlays except given one,
  // or restores all, if it was the only one displayed (or none was displayed).
  _toggleOverlay: function (idx) {
    var info = this._layers[idx];
    if (!info || !info.overlay) {
      log.warn('Overlay not found: ', info);
      return;
    }
    var map = this._map;

    var isChecked = map.hasLayer(info.layer);
    var checked = 0;
    var overlays = this._layers.filter(this._filterOverlays);
    overlays.forEach(function (el) {
      if (map.hasLayer(el.layer)) { checked++; }
    });

    if (checked === 0 || isChecked && checked === 1) {
      // if nothing is selected, or specified overlay is exclusive,
      // assume all boxes should be checked again
      overlays.forEach(function (el) {
        if (el.layer._chooser.default) {
          map.addLayer(el.layer);
        }
      });
    } else {
      // uncheck all, check specified
      overlays.forEach(function (el) {
        if (el.layer === info.layer) {
          map.addLayer(el.layer);
        } else {
          map.removeLayer(el.layer);
        }
      });
    }
  },

  _stripHtmlTags: function (str) {
    return str.replace(/(<([^>]+)>)/gi, ''); // https://css-tricks.com/snippets/javascript/strip-html-tags-in-javascript/
  },

  // !!deprecated
  getLayers: function () {
    var baseLayers = [];
    var overlayLayers = [];
    this._layers.forEach(function (info, idx) {
      (info.overlay ? overlayLayers : baseLayers).push({
        layerId: idx,
        name: this._stripHtmlTags(info.name), // IITCm does not support html in layers labels
        active: this._map.hasLayer(info.layer)
      });
    }, this);

    return {
      baseLayers: baseLayers,
      overlayLayers: overlayLayers
    };
  }
});

window.LayerChooser = LayerChooser;

function debounce (callback, time) { // https://gist.github.com/nmsdvid/8807205#gistcomment-2641356
  var timeout;
  return function () {
    var context = this;
    var args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(function () {
      timeout = null;
      callback.apply(context, args);
    }, time);
  };
}

if (typeof android !== 'undefined' && android && android.setLayers) {
  // hook some additional code into the LayerControl so it's easy for the mobile app to interface with it
  LayerChooser.include({
    _setAndroidLayers: debounce(function () { // update layer menu in IITCm
      var l = this.getLayers();
      android.setLayers(JSON.stringify(l.baseLayers), JSON.stringify(l.overlayLayers));
    }, 1000),

    setLabel: (function (setLabel) {
      return function () {
        this._setAndroidLayers();
        return setLabel.apply(this, arguments);
      };
    })(LayerChooser.prototype.setLabel),

    _update: function () {
      this._setAndroidLayers();
      return L.Control.Layers.prototype._update.apply(this, arguments);
    }
  });
}

// contains current status(on/off) of overlay layerGroups.
// !!deprecated: use `map.hasLayer` instead (https://leafletjs.com/reference.html#map-haslayer)
window.overlayStatus = {}; // to be set in constructor

// Reads recorded layerGroup status (as it may not be added to map yet),
// return `defaultDisplay` if no record found.
// !!deprecated: for most use cases prefer `getLayer()` method
// or `map.hasLayer` (https://leafletjs.com/reference.html#map-haslayer)
// window.isLayerGroupDisplayed = function (name, defaultDisplay) { // ...
window.isLayerGroupDisplayed = L.Util.falseFn; // to be set in constructor

LayerChooser.addInitHook(function () {
  window.overlayStatus = this._overlayStatus;
  window.isLayerGroupDisplayed = this._isOverlayDisplayed.bind(this);
});

// !!deprecated: use `layerChooser.addOverlay` directly
window.addLayerGroup = function (name, layerGroup, defaultDisplay) {
  var options = {default: defaultDisplay};
  if (arguments.length < 3) { options = undefined; }
  window.layerChooser.addOverlay(layerGroup, name, options);
};

// !!deprecated: use `layerChooser.removeLayer` directly
// our method differs from inherited (https://leafletjs.com/reference.html#control-layers-removelayer),
// as (by default) layer is removed from the map as well, see description for more details.
window.removeLayerGroup = function (layerGroup) {
  window.layerChooser.removeLayer(layerGroup);
};
<|endoftext|>


<commit_before>module.exports.encode = function encode(type, message) {
  if(type == null) {
    throw new Error('Type cannot be empty');
  }
  var b = new Buffer(14 + message.length);
  b.write('i3-ipc');
  b.writeUInt32BE(type, 10);
  return b;
};
<commit_msg>Message length is written to the Buffer<commit_after>module.exports.encode = function encode(type, message) {
  if(type == null) {
    throw new Error('Type cannot be empty');
  }
  var b = new Buffer(14 + message.length);
  b.write('i3-ipc');
  b.writeUInt32BE(message.length, 6);
  b.writeUInt32BE(type, 10);
  return b;
};
<|endoftext|>


<commit_before>var exec = require( 'mz/child_process' ).execFile;
var assert = require( 'assert' );

module.exports = function ( filename ) {
	return exec( 'ffprobe', [
		'-v', 'error',
		'-of', 'flat=s=_',
		'-select_streams', 'v:0',
		'-show_streams',
		filename
	] ).then( function ( out ) {
		var stdout = out[ 0 ].toString( 'utf8' );
		var width = /width=(\d+)/.exec( stdout );
		var height = /height=(\d+)/.exec( stdout );
		assert( width && height, 'No dimensions found!' );
		var codec = /codec_name=\"(\w+)\"/.exec( stdout );
		var rotate = /tags_rotate=\"(\d+)\"/.exec( stdout );
		if ( rotate ) {
			rotate = parseInt( rotate[ 1 ] );
			if ( rotate === 90 || rotate === -90 ) {
				return {
					width: parseInt( height[ 1 ] ),
					height: parseInt( width[ 1 ] ),
					codec: codec[ 1 ]
				};
			}
		}
		return {
			width: parseInt( width[ 1 ] ),
			height: parseInt( height[ 1 ] ),
			codec: codec[ 1 ]
		};
	} );
};<commit_msg>Fixed permissions<commit_after>var exec = require( 'mz/child_process' ).execFile;
var assert = require( 'assert' );

module.exports = function ( filename ) {
	return exec( 'ffprobe', [
		'-v', 'error',
		'-of', 'flat=s=_',
		'-select_streams', 'v:0',
		'-show_streams',
		filename
	] ).then( function ( out ) {
		var stdout = out[ 0 ].toString( 'utf8' );
		var width = /width=(\d+)/.exec( stdout );
		var height = /height=(\d+)/.exec( stdout );
		assert( width && height, 'No dimensions found!' );
		var codec = /codec_name=\"(\w+)\"/.exec( stdout );
		var rotate = /tags_rotate=\"(\d+)\"/.exec( stdout );
		if ( rotate ) {
			rotate = parseInt( rotate[ 1 ] );
			if ( rotate === 90 || rotate === -90 ) {
				return {
					width: parseInt( height[ 1 ] ),
					height: parseInt( width[ 1 ] ),
					codec: codec[ 1 ]
				};
			}
		}
		return {
			width: parseInt( width[ 1 ] ),
			height: parseInt( height[ 1 ] ),
			codec: codec[ 1 ]
		};
	} );
};<|endoftext|>


<commit_before>exports.parse = parse
exports.compile = compile
exports.render = render

function pad(v){for(var r='';v--;)r+=' ';return r}

function parse (source, open, close) {
  open = open || '{{'
  close = close || '}}'

  var openLength = open.length,
      closeLength = close.length,
      i = 0,
      end = source.length,
      partials = [],
      expressions = [],
      first,
      source = pad(closeLength) + source // "hack" to avoid checking starting index each itteration

  while (end > i) {
    // get index of first open
    first = source.indexOf(open, i)

    // break if there's no first open
    if (first === -1) break

    // this commented out code is the check that is avoided with the "hack"
    // partials.push(source.slice(i ? i + closeLength : 0, first))
    partials.push(source.slice(i + closeLength, first))

    // set index of close starting at first open
    i = source.indexOf(close, first)
    expressions.push(source.slice(first + openLength, i))
  }

  partials.push(source.slice(i + closeLength))
  return [partials, expressions]
}

var NODATA = {}
function compile (template, data) {
  data = data || NODATA
  var source = '', partials = template[0], expressions = template[1], i = 0, end = expressions.length
  while (i < end) source += partials[i] + (data[expressions[i++]] || '')
  return source + partials[i]
}

function render (source, data, open, close) {
  return compile(parse(source, open, close), data)
}
<commit_msg>Faster parsing implementation<commit_after>exports.parse = parse
exports.compile = compile
exports.render = render

function parse (source, open, close) {
  open = open || '{{'
  close = close || '}}'

  var openLength = open.length,
      closeLength = close.length,
      partials = [],
      expressions = [],
      first,
      last

  for (;;) {
    first = source.indexOf(open)
    if (first < 0) break
    partials.push(source.slice(0, first))
    last = source.indexOf(close, first)
    expressions.push(source.slice(first + openLength, last))
    source = source.slice(last + closeLength)
  }

  partials.push(source)

  return [partials, expressions]
}

var NODATA = {}
function compile (template, data) {
  data = data || NODATA
  var source = '', partials = template[0], expressions = template[1], i = 0, end = expressions.length
  while (i < end) source += partials[i] + (data[expressions[i++]] || '')
  return source + partials[i]
}

function render (source, data, open, close) {
  return compile(parse(source, open, close), data)
}
<|endoftext|>


<commit_before>'use strict';

let test = require('tape');
let cliParse = require('../lib/group-parse');

test('group-parse: serial', (t) => {
    let result = cliParse(['one', 'two', 'three'], {
        one: 'ls',
        two: 'pwd',
        three: 'whoami'
    });

    t.equal(result, 'ls && pwd && whoami', 'should build cmd line');

    t.end();
});

test('group-parse: parallel', (t) => {
    let result = cliParse(['one', 'two', 'three'], {parallel: true}, {
        one: 'ls',
        two: 'pwd',
        three: 'whoami'
    });

    t.equal(result, 'ls & pwd & whoami', 'should build cmd line');

    t.end();
});
<commit_msg>refactor(group-parse) cliParse -> groupParse<commit_after>'use strict';

let test = require('tape');
let groupParse = require('../lib/group-parse');

test('group-parse: serial', (t) => {
    let result = groupParse(['one', 'two', 'three'], {
        one: 'ls',
        two: 'pwd',
        three: 'whoami'
    });

    t.equal(result, 'ls && pwd && whoami', 'should build cmd line');

    t.end();
});

test('group-parse: parallel', (t) => {
    let result = groupParse(['one', 'two', 'three'], {parallel: true}, {
        one: 'ls',
        two: 'pwd',
        three: 'whoami'
    });

    t.equal(result, 'ls & pwd & whoami', 'should build cmd line');

    t.end();
});
<|endoftext|>


<commit_before>/**
 * @providesModule Notifications
 */

'use strict';

import { NativeModules, DeviceEventEmitter } from 'react-native';

const { RNOneSignal } = NativeModules;

var Notifications = {
	onError: false,
	onNotificationOpened: false,
};

var _pendingNotifications = [];
var DEVICE_NOTIF_EVENT = 'remoteNotificationOpened';

/**
 * Configure local and remote notifications
 * @param {Object}		options
 * @param {function}	options.onNotificationOpened - Fired when a remote notification is received.
 * @param {function} 	options.onError - None
 */
Notifications.configure = function(options: Object) {
	if ( typeof options.onError !== 'undefined' ) {
		this.onError = options.onError;
	}

	if ( typeof options.onNotificationOpened !== 'undefined' ) {
		this.onNotificationOpened = options.onNotificationOpened;

		if (_pendingNotifications.length > 0) {
			var notification = _pendingNotifications.pop();
			this._onNotificationOpened(notification.message, notification.data, notification.isActive);
		}
	}
};

/* Unregister */
Notifications.unregister = function() {
	this.onNotificationOpened = false;
};

Notifications._onNotificationOpened = function(message, data, isActive) {
	if ( this.onNotificationOpened === false ) {
		var notification = {message: message, data: data, isActive: isActive};
		_pendingNotifications.push(notification);
		return;
	}
	this.onNotificationOpened(message, data, isActive);
};

Notifications.sendTag = function(key, value) {
	RNOneSignal.sendTag(key, value);
};

Notifications.sendTags = function(tags) {
	RNOneSignal.sendTags(tags || {});
};

Notifications.getTags = function(next) {
	RNOneSignal.getTags(next);
}

Notifications.deleteTag = function(key) {
	RNOneSignal.deleteTag(key);
}

Notifications.idsAvailable = function(idsAvailable) {
	RNOneSignal.idsAvailable(idsAvailable);
}

DeviceEventEmitter.addListener(DEVICE_NOTIF_EVENT, function(notifData) {
		var message = notifData.message;
		var data = (notifData.additionalData !== null && typeof notifData.additionalData === 'object') ? notifData.additionalData : JSON.parse(notifData.additionalData);
		var isActive = notifData.isActive;
		Notifications._onNotificationOpened(message, data, isActive);
	}
);

module.exports = Notifications;
<commit_msg>add JS implementation<commit_after>/**
 * @providesModule Notifications
 */

'use strict';

import { NativeModules, DeviceEventEmitter } from 'react-native';

const { RNOneSignal } = NativeModules;

var Notifications = {
	onError: false,
	onNotificationOpened: false,
};

var _pendingNotifications = [];
var DEVICE_NOTIF_EVENT = 'remoteNotificationOpened';

/**
 * Configure local and remote notifications
 * @param {Object}		options
 * @param {function}	options.onNotificationOpened - Fired when a remote notification is received.
 * @param {function} 	options.onError - None
 */
Notifications.configure = function(options: Object) {
	if ( typeof options.onError !== 'undefined' ) {
		this.onError = options.onError;
	}

	if ( typeof options.onNotificationOpened !== 'undefined' ) {
		this.onNotificationOpened = options.onNotificationOpened;

		if (_pendingNotifications.length > 0) {
			var notification = _pendingNotifications.pop();
			this._onNotificationOpened(notification.message, notification.data, notification.isActive);
		}
	}
};

/* Unregister */
Notifications.unregister = function() {
	this.onNotificationOpened = false;
};

Notifications._onNotificationOpened = function(message, data, isActive) {
	if ( this.onNotificationOpened === false ) {
		var notification = {message: message, data: data, isActive: isActive};
		_pendingNotifications.push(notification);
		return;
	}
	this.onNotificationOpened(message, data, isActive);
};

Notifications.sendTag = function(key, value) {
	RNOneSignal.sendTag(key, value);
};

Notifications.sendTags = function(tags) {
	RNOneSignal.sendTags(tags || {});
};

Notifications.getTags = function(next) {
	RNOneSignal.getTags(next);
};

Notifications.deleteTag = function(key) {
	RNOneSignal.deleteTag(key);
};

Notifications.enableVibrate = function(enable) {
	RNOneSignal.enableVibrate(enable);
};

Notifications.enableSound = function(enable) {
	RNOneSignal.enableSound(enable);
};

Notifications.enableNotificationsWhenActive = function(enable) {
	RNOneSignal.enableNotificationsWhenActive(enable);
};

Notifications.enableNotificationsWhenActive = function(enable) {
	RNOneSignal.enableNotificationsWhenActive(enable);
};

Notifications.setSubscription = function(enable) {
	RNOneSignal.setSubscription(enable);
};

Notifications.idsAvailable = function(idsAvailable) {
	RNOneSignal.idsAvailable(idsAvailable);
}

DeviceEventEmitter.addListener(DEVICE_NOTIF_EVENT, function(notifData) {
		var message = notifData.message;
		var data = (notifData.additionalData !== null && typeof notifData.additionalData === 'object') ? notifData.additionalData : JSON.parse(notifData.additionalData);
		var isActive = notifData.isActive;
		Notifications._onNotificationOpened(message, data, isActive);
	}
);

module.exports = Notifications;
<|endoftext|>


<commit_before>const _ = require('lodash')
const got = require('got')
const fs = require('fs')
const path = require('path')

var config
try {
  config = JSON.parse(fs.readFileSync(path.resolve('oh-my-cdn.json')))
} catch (err) {
  console.error(err)
  process.exit(1)
}
console.log('Oh My CDN is fetching your targets now...')

var directory = config.directory == null ? 'vendor' : config.directory
if (config.targets == null) {
  console.log('No targets to fetch.')
  process.exit(0)
}
var targets = config.targets
try {
  fs.mkdirSync(path.resolve(directory))
} catch (err) {
  if (err.code !== 'EEXIST') {
    throw err
  }
}

var promises = []
_.forIn(targets, function fetchScripts (value, key) {
  var promise = new Promise((resolve, reject) => {
    var url, filename
    if (_.isString(value)) {
      url = value
      filename = key + path.extname(value)
    } else {
      url = value.url
      filename = value.filename
    }

    var writeStream = fs.createWriteStream(path.resolve(directory, filename))
    var fetchStream = got.stream(url)
      .on('error', () => {
        // Resolve it anyway
        console.log('Failed to fetch >> ' + filename)
        resolve(null)
      })
    fetchStream.pipe(writeStream)
      .on('finish', () => {
        console.log('Fetched >> ' + filename)
        resolve(null)
      })
      .on('error', (err) => {
        console.log('Failed to write >> ' + filename)
        reject(err)
      })
  })
  promises.push(promise)
})

Promise.all(promises)
  .then(() => {
    console.log('DONE')
    process.exit(0)
  })
  .catch((err) => {
    console.error(err)
    process.exit(1)
  })
<commit_msg>show simple message rather than log error when config file does not found<commit_after>const _ = require('lodash')
const got = require('got')
const fs = require('fs')
const path = require('path')

var config
try {
  config = JSON.parse(fs.readFileSync(path.resolve('oh-my-cdn.json')))
} catch (err) {
  console.error('oh-my-cdn.json doesn\'t exist.')
  process.exit(1)
}
console.log('Oh My CDN is fetching your targets now...')

var directory = config.directory == null ? 'vendor' : config.directory
if (config.targets == null) {
  console.log('No targets to fetch.')
  process.exit(0)
}
var targets = config.targets
try {
  fs.mkdirSync(path.resolve(directory))
} catch (err) {
  if (err.code !== 'EEXIST') {
    throw err
  }
}

var promises = []
_.forIn(targets, function fetchScripts (value, key) {
  var promise = new Promise((resolve, reject) => {
    var url, filename
    if (_.isString(value)) {
      url = value
      filename = key + path.extname(value)
    } else {
      url = value.url
      filename = value.filename
    }

    var writeStream = fs.createWriteStream(path.resolve(directory, filename))
    var fetchStream = got.stream(url)
      .on('error', () => {
        // Resolve it anyway
        console.log('Failed to fetch >> ' + filename)
        resolve(null)
      })
    fetchStream.pipe(writeStream)
      .on('finish', () => {
        console.log('Fetched >> ' + filename)
        resolve(null)
      })
      .on('error', (err) => {
        console.log('Failed to write >> ' + filename)
        reject(err)
      })
  })
  promises.push(promise)
})

Promise.all(promises)
  .then(() => {
    console.log('DONE')
    process.exit(0)
  })
  .catch((err) => {
    console.error(err)
    process.exit(1)
  })
<|endoftext|>