<commit_before>var Px = (function () {
    'use strict';

    function Px(pxString) {
	this.ctor(pxString);
    }

    function arrayOfZeroes(l) {
	var a = [];
	while (l--) {
	    a[l] = 0;
	}
	return a;
    }

    Px.prototype = {

	keyword: function(k) {
	    if (! _.include(this.keywords(), k)) {
	    	throw ('\'' + k + '\' is not a valid KEYWORD');
	    }

	    if (this.metadata[k].TABLE) {
		return this.metadata[k].TABLE;
	    }
	    else {
		return this.metadata[k];
	    }
	},

	title: function() {
	    return this.keyword('TITLE');
	},

	keywords: function() {
	    return _.keys(this.metadata);
	},

	variables: function() {
	    return _.flatten([this.keyword('STUB'), this.keyword('HEADING')]);
	},

	variable: function(v) {
	    var vars = this.variables();
	    if (typeof(v) === 'number') {
		return vars[v];
	    }
	    else if (typeof(v) === 'string') {
		return _.indexOf(vars, v);
	    }
	    else if (_.isRegExp(v)) {
		return _.indexOf(vars, _.find(vars, function(el) { return el.match(v); }));
	    }
	    else {
		return undefined;
	    }
	},

	values: function(v) {
	    var varName =
		typeof(v) === 'number'
		? this.variable(v)
		: this.variables()[this.variable(v)];

	    return this.keyword('VALUES')[varName];
	},

	/// TODO Return values if no codes (e.g. for year)
	codes: function(v) {
	    var varName =
		typeof(v) === 'number'
		? this.variable(v)
		: this.variables()[this.variable(v)];

	    return this.keyword('CODES')[varName];
	},

	valCounts: function() {
	    var counts = [];
	    _.each(this.variables(), function(e,i) { counts.push(_.size(this.values(i))); }, this);
	    return counts;
	},

	value: function() {

	},

	code: function() {

	},

	datum: function(s) {
	    // TODO: check for correct array length
	    var counts = this.valCounts();

	    var index = 0;
	    for (var i = 0, len = s.length - 1; i < len; i++) {
		index += s[i] * ( _.reduce( counts.slice(i+1), function(a, b) { return a * b; }) );
	    }
	    index += _.last(s);

	    // if true is passed as 2nd arg index rather than value is returned
	    if (arguments[1] === true) {
		return index;
	    }
	    else {
		return +this.data[index];
	    }
	},

	datacol: function(s) {
	    // TODO: check for correct array length (s) and check for 1 and only 1 wildcard
	    var counts = this.valCounts();

	    var datacol = [];
	    var grpIdx = _.indexOf(s, '*');

	    for (var i = 0; i < counts[grpIdx]; i++) {
		s[grpIdx] = i;

		// if true is passed as 2nd arg indices rather than values are returned
		datacol.push(this.datum(s,arguments[1]));
	    }
	    return datacol;
	},

	datadict: function(s) {

	    var datadict = {},
	        grpIdx = _.indexOf(s, '*'),
	        codes = this.codes(grpIdx),
	        datacol = this.datacol(s);

	    _.each(datacol, function(d, i) {
		datadict[codes[i]] = d;
	    });

	    return datadict;
	},

	datatable: function(s) {
	    // This is fairly - useless remove 2-dimension limit

	    // TODO: check for correct array length (s) and check for 2 and only 2 wildcard
	    // ALSO: allow either wildcard be the grouping variable (the STUB)
	    var counts = this.valCounts();

	    var grpIdxs = [];
	    _.each(s, function(el, i) { if (el === '*') grpIdxs.push(i); });

	    var grpIdx = grpIdxs[0];
	    var chgIdx = _.last(grpIdxs);

	    var datatable = [];

	    for (var i = 0; i < counts[chgIdx]; i++) {
		s[grpIdx] = '*';
		s[chgIdx] = i;
		console.log(s);
		datatable.push(this.datacol(s, arguments[1]));
	    }
	    return datatable;
	},

	explicitDatatable: function() {
	    // TODO -- check for data array size and if too big either (a) split the job, (b) throw an exception and suggest a truncate or subset operation first

	    var counts = this.valCounts(),
	        vars = this.variables(),
	        valIdx = arrayOfZeroes(counts.length),
	        last = valIdx.length - 1,
	        multipliers = [],
	        dataset = [];

	    for (var i = 0, l = counts.length; i < l - 1; i++) {
		// the multiplier for each variable is the product of the numbers of values
		// for each variable occuring after it in the variables array
		multipliers[i] = _.reduce(counts.slice(i+1), function(a, b) { return a * b; });
	    }

	    _.each(this.data, function(d, i) {

		// create datum object and push onto dataset
		var datum = {num: d};
		for (var di = 0, dl = vars.length; di < dl; di++) {
		    datum[vars[di]] = this.values(di)[valIdx[di]];
		}
		dataset.push(datum);

		// increment indices:
		for (var mi = 0, ml = multipliers.length; mi < ml; mi++) {
	    	    if ( (i + 1) % (multipliers[mi]) === 0 ) {
	    		valIdx[mi] = valIdx[mi] === counts[mi] - 1 ? 0 : valIdx[mi] + 1;
	    	    };
		}
	    	valIdx[last] = valIdx[last] === counts[last] - 1 ? 0 : valIdx[last] + 1;

	    }, this);

	    return dataset;
	},

	// remove values and associated data from this object
	truncate: function(s) {
	    //TODO: validate array length

	    var counts = this.valCounts(),
	    multipliers = [];

	    _.each(s, function(d, i) {
		if (d[0] === '*') {
		    s[i] = _.range(0, counts[i] - 1);
		}
	    });

	    for (var i = 0, l = counts.length; i < l - 1; i++) {
		multipliers[i] = _.reduce(counts.slice(i+1), function(a, b) { return a * b; });
	    }
	    multipliers.push(1);

	    for (var i = 0, l = s.length; i < l; i++) {

		// drop the values
	    	this.metadata.VALUES[this.variables()[i]] =
	    	    _.filter(this.metadata.VALUES[this.variables()[i]], function(e, j) {
			return _.indexOf(s[i], j) !== -1;
		    });
	    }

	    var keepIdxs = [];
	    var pattern = function pattern(c,m,w,p) {
		if (c.length > 1) {
	    	    p = typeof p !== 'undefined' ? p : 1;
	    	    var count = c.pop(),
	    	    multiple = m.pop(),
	    	    want = w.pop();

	    	    var patt = _.flatten(_.map(_.range(0, count), function(d) {
			return _.include(want, d) ? p : arrayOfZeroes(multiple);
		    }));

	    	    pattern(c, m, w, patt);
		}
		keepIdxs.push(_.flatten(p));
	    };
	    pattern(counts,multipliers,s);
	    keepIdxs = keepIdxs[0];

	    var indices = [];
	    _.each(s[0], function(d) {
	    	var start = d * multipliers[0];
	    	var end = start + multipliers[0];
	    	indices.push(_.filter(_.range(start, end), function(d, i) {return keepIdxs[i] === 1;}));
	    });
	    indices = _.flatten(indices);
	    this.data = _.filter(this.data, function(d, i) {return _.indexOf(indices, i) !== -1;});
	},

	// create a new object from a subset of values in this object
	subset: function(s) {
	    // TODO
	},

	ctor: function(pxString) {
	    var metadata = {};
	    var data;

	    var pxSplit = pxString.split(/\nDATA=/);

	    var pxMetadata =
		pxSplit[0]
		.replace(/;\s+(\r\n|\r|\n)/g, ';;')
		.replace(/(\r\n|\r|\n)/g, '')
		.replace(/""/g, ' ')
		.split(';;');

	    var pxData = pxSplit[1];

	    // parse metadata
	    for (var i = 0, pxLen = pxMetadata.length; i < pxLen; i++) {

		var keyOptVal =
		    pxMetadata[i]
		    .match(/^(.+?)(?:\((.+?)\))?=(.+)$/);

		if (!keyOptVal[2]) keyOptVal[2] = 'TABLE';

		var key = keyOptVal[1];
		var vals = keyOptVal[3].replace(/^"|"$/g, '').split(/","/g);
		var opt = keyOptVal[2].replace(/"/g, '');

		if (!metadata[key]) metadata[key] = {};

		metadata[key][opt] = vals.length === 1 ? vals[0] : vals;

	    }

	    // parse data
	    data =
		pxData
		.replace(/(\r\n|\r|\n)/g, '')
		.replace(';', '')
		.split(/\s+/);

	    // Fix for bad files with no HEADING
	    if (!metadata.HEADING) {
		metadata.HEADING = {TABLE: []};
	    }

	    // Assign instance data
	    this.metadata = metadata;
	    this.data = data;
	}

    }

    return Px;

}());

<commit_msg>fixed bug where single value VALUES options were breaking mapdata.js --- they are now assigned to one element arrays<commit_after>var Px = (function () {
    'use strict';

    function Px(pxString) {
	this.ctor(pxString);
    }

    function arrayOfZeroes(l) {
	var a = [];
	while (l--) {
	    a[l] = 0;
	}
	return a;
    }

    Px.prototype = {

	keyword: function(k) {
	    if (! _.include(this.keywords(), k)) {
	    	throw ('\'' + k + '\' is not a valid KEYWORD');
	    }

	    if (this.metadata[k].TABLE) {
		return this.metadata[k].TABLE;
	    }
	    else {
		return this.metadata[k];
	    }
	},

	title: function() {
	    return this.keyword('TITLE');
	},

	keywords: function() {
	    return _.keys(this.metadata);
	},

	variables: function() {
	    return _.flatten([this.keyword('STUB'), this.keyword('HEADING')]);
	},

	variable: function(v) {
	    var vars = this.variables();
	    if (typeof(v) === 'number') {
		return vars[v];
	    }
	    else if (typeof(v) === 'string') {
		return _.indexOf(vars, v);
	    }
	    else if (_.isRegExp(v)) {
		return _.indexOf(vars, _.find(vars, function(el) { return el.match(v); }));
	    }
	    else {
		return undefined;
	    }
	},

	values: function(v) {
	    var varName =
		typeof(v) === 'number'
		? this.variable(v)
		: this.variables()[this.variable(v)];

	    return this.keyword('VALUES')[varName];
	},

	/// TODO Return values if no codes (e.g. for year)
	codes: function(v) {
	    var varName =
		typeof(v) === 'number'
		? this.variable(v)
		: this.variables()[this.variable(v)];

	    return this.keyword('CODES')[varName];
	},

	valCounts: function() {
	    var counts = [];
	    _.each(this.variables(), function(e,i) { counts.push(_.size(this.values(i))); }, this);
	    return counts;
	},

	value: function() {

	},

	code: function() {

	},

	datum: function(s) {
	    // TODO: check for correct array length
	    var counts = this.valCounts();

	    var index = 0;
	    for (var i = 0, len = s.length - 1; i < len; i++) {
		index += s[i] * ( _.reduce( counts.slice(i+1), function(a, b) { return a * b; }) );
	    }
	    index += _.last(s);

	    // if true is passed as 2nd arg index rather than value is returned
	    if (arguments[1] === true) {
		return index;
	    }
	    else {
		return +this.data[index];
	    }
	},

	datacol: function(s) {
	    // TODO: check for correct array length (s) and check for 1 and only 1 wildcard
	    var counts = this.valCounts();

	    var datacol = [];
	    var grpIdx = _.indexOf(s, '*');

	    for (var i = 0; i < counts[grpIdx]; i++) {
		s[grpIdx] = i;

		// if true is passed as 2nd arg indices rather than values are returned
		datacol.push(this.datum(s,arguments[1]));
	    }
	    return datacol;
	},

	datadict: function(s) {

	    var datadict = {},
	        grpIdx = _.indexOf(s, '*'),
	        codes = this.codes(grpIdx),
	        datacol = this.datacol(s);

	    _.each(datacol, function(d, i) {
		datadict[codes[i]] = d;
	    });

	    return datadict;
	},

	datatable: function(s) {
	    // This is fairly - useless remove 2-dimension limit

	    // TODO: check for correct array length (s) and check for 2 and only 2 wildcard
	    // ALSO: allow either wildcard be the grouping variable (the STUB)
	    var counts = this.valCounts();

	    var grpIdxs = [];
	    _.each(s, function(el, i) { if (el === '*') grpIdxs.push(i); });

	    var grpIdx = grpIdxs[0];
	    var chgIdx = _.last(grpIdxs);

	    var datatable = [];

	    for (var i = 0; i < counts[chgIdx]; i++) {
		s[grpIdx] = '*';
		s[chgIdx] = i;
		console.log(s);
		datatable.push(this.datacol(s, arguments[1]));
	    }
	    return datatable;
	},

	explicitDatatable: function() {
	    // TODO -- check for data array size and if too big either (a) split the job, (b) throw an exception and suggest a truncate or subset operation first

	    var counts = this.valCounts(),
	        vars = this.variables(),
	        valIdx = arrayOfZeroes(counts.length),
	        last = valIdx.length - 1,
	        multipliers = [],
	        dataset = [];

	    for (var i = 0, l = counts.length; i < l - 1; i++) {
		// the multiplier for each variable is the product of the numbers of values
		// for each variable occuring after it in the variables array
		multipliers[i] = _.reduce(counts.slice(i+1), function(a, b) { return a * b; });
	    }

	    _.each(this.data, function(d, i) {

		// create datum object and push onto dataset
		var datum = {num: d};
		for (var di = 0, dl = vars.length; di < dl; di++) {
		    datum[vars[di]] = this.values(di)[valIdx[di]];
		}
		dataset.push(datum);

		// increment indices:
		for (var mi = 0, ml = multipliers.length; mi < ml; mi++) {
	    	    if ( (i + 1) % (multipliers[mi]) === 0 ) {
	    		valIdx[mi] = valIdx[mi] === counts[mi] - 1 ? 0 : valIdx[mi] + 1;
	    	    };
		}
	    	valIdx[last] = valIdx[last] === counts[last] - 1 ? 0 : valIdx[last] + 1;

	    }, this);

	    return dataset;
	},

	// remove values and associated data from this object
	truncate: function(s) {
	    //TODO: validate array length

	    var counts = this.valCounts(),
	    multipliers = [];

	    _.each(s, function(d, i) {
		if (d[0] === '*') {
		    s[i] = _.range(0, counts[i] - 1);
		}
	    });

	    for (var i = 0, l = counts.length; i < l - 1; i++) {
		multipliers[i] = _.reduce(counts.slice(i+1), function(a, b) { return a * b; });
	    }
	    multipliers.push(1);

	    for (var i = 0, l = s.length; i < l; i++) {

		// drop the values
	    	this.metadata.VALUES[this.variables()[i]] =
	    	    _.filter(this.metadata.VALUES[this.variables()[i]], function(e, j) {
			return _.indexOf(s[i], j) !== -1;
		    });
	    }

	    var keepIdxs = [];
	    var pattern = function pattern(c,m,w,p) {
		if (c.length > 1) {
	    	    p = typeof p !== 'undefined' ? p : 1;
	    	    var count = c.pop(),
	    	    multiple = m.pop(),
	    	    want = w.pop();

	    	    var patt = _.flatten(_.map(_.range(0, count), function(d) {
			return _.include(want, d) ? p : arrayOfZeroes(multiple);
		    }));

	    	    pattern(c, m, w, patt);
		}
		keepIdxs.push(_.flatten(p));
	    };
	    pattern(counts,multipliers,s);
	    keepIdxs = keepIdxs[0];

	    var indices = [];
	    _.each(s[0], function(d) {
	    	var start = d * multipliers[0];
	    	var end = start + multipliers[0];
	    	indices.push(_.filter(_.range(start, end), function(d, i) {return keepIdxs[i] === 1;}));
	    });
	    indices = _.flatten(indices);
	    this.data = _.filter(this.data, function(d, i) {return _.indexOf(indices, i) !== -1;});
	},

	// create a new object from a subset of values in this object
	subset: function(s) {
	    // TODO
	},

	ctor: function(pxString) {
	    var metadata = {};
	    var data;

	    var pxSplit = pxString.split(/\nDATA=/);

	    var pxMetadata =
		pxSplit[0]
		.replace(/;\s+(\r\n|\r|\n)/g, ';;')
		.replace(/(\r\n|\r|\n)/g, '')
		.replace(/""/g, ' ')
		.split(';;');

	    var pxData = pxSplit[1];

	    // parse metadata
	    for (var i = 0, pxLen = pxMetadata.length; i < pxLen; i++) {

		var keyOptVal =
		    pxMetadata[i]
		    .match(/^(.+?)(?:\((.+?)\))?=(.+)$/);

		if (!keyOptVal[2]) keyOptVal[2] = 'TABLE';

		var key = keyOptVal[1];
		var vals = keyOptVal[3].replace(/^"|"$/g, '').split(/","/g);
		var opt = keyOptVal[2].replace(/"/g, '');

		if (!metadata[key]) metadata[key] = {};

		if (key !== 'VALUES') { // ensure that a single VALUES otion still gets assigned to an array
		    metadata[key][opt] = vals.length === 1 ? vals[0] : vals;
		}
		else {
		    metadata[key][opt] = vals;
		}

	    }

	    // parse data
	    data =
		pxData
		.replace(/(\r\n|\r|\n)/g, '')
		.replace(';', '')
		.split(/\s+/);

	    // Fix for bad files with no HEADING
	    if (!metadata.HEADING) {
		metadata.HEADING = {TABLE: []};
	    }

	    // Assign instance data
	    this.metadata = metadata;
	    this.data = data;
	}

    }

    return Px;

}());

<|endoftext|>


<commit_before>var SuperAsana = {
	/**
	 * Add the superman logo to Asana Logo
	 */
	addLogo: function() {
		var url = 'http://cdn.mysitemyway.com/etc-mysitemyway/icons/legacy-previews/icons-256/blue-jelly-icons-business/078583-blue-jelly-icon-business-logo-superman-sc37.png';

		Zepto('#logo').prepend('');
	},

	/**
	 * Initializes the SuperAsana after loaded dependencies
	 */
	init: function() {
		//Add the logo
		this.addLogo();

		//Changes the original PushState function
		//to intercept location changes
		var originalPushState = history.pushState;

		history.pushState = function() {
			originalPushState.apply(this, arguments);

			var argumentsArray = [];

			//Convert arguments object to array
			for (var i in arguments) {
				argumentsArray.push(arguments[i]);
			}

			Zepto(document).trigger('location:change', argumentsArray);
		}

		Zepto(document).on('location:change', function(e, state, title, url) {
			SuperAsana.route(url);
		});

		//Window resize redraws the page, we need to render again
		Zepto(window).on('resize', function() {
			SuperAsana.route(window.location.pathname);
		});

		//Execute the initial route based on the window location pathname
		SuperAsana.route(window.location.pathname);
	},

	/**
	 * Load the dependencies scripts
	 */
	loadScripts: function() {
		//Loads Zepto
		var zeptojs = document.createElement('script');
		zeptojs.type = 'text/javascript';
		zeptojs.src = 'https://cdnjs.cloudflare.com/ajax/libs/zepto/1.1.6/zepto.min.js';
		document.body.appendChild(zeptojs);

		var waitCounter = 0;

		//Waits for Zepto
		var zeptoInterval = setInterval(function() {
			if (Zepto) {
				clearInterval(zeptoInterval);
				//Initialize Super Asana
				SuperAsana.init();
			}

			//Greater then 10 seconds
			//Cancel the waiting, and add Zepto again
			if (waitCounter > 100) {
				clearInterval(zeptoInterval);
				SuperAsana.loadScripts();
			}

			waitCounter++;
		}, 100);
	},

	pages: {
		taskList: {
			/**
			 * Add a project overview section box with pomodoro details
			 * @param {float}   completed  Number of completed pomodoros
			 * @param {float}   total      Total of pomodoros
			 */
			addProjectPomodoroSection: function(completed, total) {
				Zepto('.project-overview').prepend('' +
					'
' +
						'
POMODORO
' +
						'
' +
							'
' +
								'
' +
									'' + completed + '' +
									'Pomodoros Completed' +
								'
' +
								'
' +
									'' + total + '' +
									'Pomodoros Total' +
								'
' +
							'
' +
						'
' +
					'
' +
				'');
			},

			/**
			 * Initializes the page
			 */
			init: function() {
				//Find all tasks
				var tasks = Zepto('.task-row'),
					//The task has a Pomodoro text? Check for the pattern:
					//[ 1 / 1 ] - Task name
					//[ 1 / x ] - Task name
					//[ 1.5 / x ] - Task name
					pomodoroRegex = /\[(\s*\d*\s*)\/(\s*(\d*|x)\s*)\](\s*)-(\s*)(.*)/,
					//Number of pomodoros completed
					completed = 0,
					//Total of pomodoros
					total = 0;

				tasks.each(function() {
					var textarea = Zepto(this).find('textarea'),
						matchResult = textarea.val().match(pomodoroRegex);

					//If it's a pomodoro task, sum the pomodoros values
					if (matchResult) {
						completed += parseFloat(matchResult[1]);

						if (matchResult[2].trim() != 'x') {
							total += parseFloat(matchResult[2]);
						}
					}
				});

				this.addProjectPomodoroSection(completed, total);
			}
		}
	},

	/**
	 * Initializes the correct page for the current url
	 * @param  {string} url Window pathname
	 */
	route: function(url) {
		// Tasks List URL => '/0/1234567890123/list'
		var listRegex = /\/0\/([0-9]*)\/list/;

		if (listRegex.test(url)) {
			SuperAsana.pages.taskList.init();
		}
	},
}

SuperAsana.loadScripts();<commit_msg>Started the pomodoro timer<commit_after>var SuperAsana = {
	/**
	 * Add the superman logo to Asana Logo
	 */
	addLogo: function() {
		var url = SuperAsana.Images.logo;

		Zepto('#logo').prepend('');
	},

	Images: {
		logo: 'http://cdn.mysitemyway.com/etc-mysitemyway/icons/legacy-previews/icons-256/blue-jelly-icons-business/078583-blue-jelly-icon-business-logo-superman-sc37.png',
		tomato: 'http://www.veryicon.com/icon/16/Food%20%26%20Drinks/Vegetables/Tomato.png'
	},

	/**
	 * Initializes the SuperAsana after loaded dependencies
	 */
	init: function() {
		//Add the logo
		this.addLogo();

		//Changes the original PushState function
		//to intercept location changes
		var originalPushState = history.pushState;

		history.pushState = function() {
			originalPushState.apply(this, arguments);

			var argumentsArray = [];

			//Convert arguments object to array
			for (var i in arguments) {
				argumentsArray.push(arguments[i]);
			}

			Zepto(document).trigger('location:change', argumentsArray);
		}

		Zepto(document).on('location:change', function(e, state, title, url) {
			SuperAsana.route(url);
		});

		//Window resize redraws the page, we need to render again
		Zepto(window).on('resize', function() {
			SuperAsana.route(window.location.pathname);
		});

		//Execute the initial route based on the window location pathname
		SuperAsana.route(window.location.pathname);
	},

	/**
	 * Load the dependencies scripts
	 */
	loadScripts: function() {
		//Loads Zepto
		var zeptojs = document.createElement('script');
		zeptojs.type = 'text/javascript';
		zeptojs.src = 'https://cdnjs.cloudflare.com/ajax/libs/zepto/1.1.6/zepto.min.js';
		document.body.appendChild(zeptojs);

		var waitCounter = 0;

		//Waits for Zepto
		var zeptoInterval = setInterval(function() {
			if (Zepto) {
				clearInterval(zeptoInterval);
				//Initialize Super Asana
				SuperAsana.init();
			}

			//Greater then 10 seconds
			//Cancel the waiting, and add Zepto again
			if (waitCounter > 100) {
				clearInterval(zeptoInterval);
				SuperAsana.loadScripts();
			}

			waitCounter++;
		}, 100);
	},

	Pages: {
		/**
		 * The details page of a task
		 */
		TaskDetails: {
			addPomodoroCounterButton: function() {
				var iconUrl = SuperAsana.Images.tomato,
					self = this;

				Zepto('.property.description.taskDetailsView-description').after('' +
					'
' +
						'START' +
					'
' +
				'');

				Zepto('.super.pomodoro.button').on('click', function(e) {
					SuperAsana.Pomodoro.startForTask(self.getCurrentProjectId(), self.getCurrentTaskId());
				});
			},

			getCurrentProjectId: function() {
				var pathnameSplit = window.location.pathname.split('/');

				return pathnameSplit[pathnameSplit.length - 2];
			},

			getCurrentTaskId: function() {
				var pathnameSplit = window.location.pathname.split('/');

				return pathnameSplit[pathnameSplit.length - 1];
			},

			/**
			 * Initializes the page
			 */
			init: function() {
				var superDiv = Zepto('.details-pane-body .property.super');

				if (superDiv.length == 0) {
					this.addPomodoroCounterButton();
				}
			}
		},

		/**
		 * The project page, listing all the tasks
		 */
		TaskList: {
			/**
			 * Add a project overview section box with pomodoro details
			 * @param {float}   completed  Number of completed pomodoros
			 * @param {float}   total      Total of pomodoros
			 */
			addProjectPomodoroSection: function(completed, total) {
				Zepto('.project-overview').prepend('' +
					'
' +
						'
POMODORO
' +
						'
' +
							'
' +
								'
' +
									'' + completed + '' +
									'Pomodoros Completed' +
								'
' +
								'
' +
									'' + total + '' +
									'Pomodoros Total' +
								'
' +
							'
' +
						'
' +
					'
' +
				'');
			},

			/**
			 * Initializes the page
			 */
			init: function() {
				//Find all tasks
				var tasks = Zepto('.task-row'),
					//The task has a Pomodoro text? Check for the pattern:
					//[ x / 1 ] - Task name
					//[ 1 / 1 ] - Task name
					//[ 1 / x ] - Task name
					//[ 1.5 / x ] - Task name
					pomodoroRegex = /\[(\s*\d*\s*)\/(\s*(\d*|x)\s*)\](\s*)-(\s*)(.*)/,
					//Number of pomodoros completed
					completed = 0,
					//Total of pomodoros
					total = 0;

				tasks.each(function() {
					var textarea = Zepto(this).find('textarea'),
						matchResult = textarea.val().match(pomodoroRegex);

					//If it's a pomodoro task, sum the pomodoros values
					if (matchResult) {
						if (matchResult[1].trim() != 'x') {
							completed += parseFloat(matchResult[1]);
						}

						if (matchResult[2].trim() != 'x') {
							total += parseFloat(matchResult[2]);
						}
					}
				});

				this.addProjectPomodoroSection(completed, total);
				this.markRunningTasks();
			},

			/**
			 * Add a mark to the running task on the task list
			 */
			markRunningTasks: function() {
				var iconUrl = SuperAsana.Images.tomato,
					self = this;

				//The pomodoro is running?
				if (SuperAsana.Pomodoro.current.taskId && SuperAsana.Pomodoro.current.projectId) {
					var taskTextAreaId = '#pot.' + SuperAsana.Pomodoro.current.projectId + '_row__main_input__SPECIAL_GROUP_NULL_' +
						SuperAsana.Pomodoro.current.taskId,
						textarea = Zepto(taskTextAreaId);

					//The textarea exists?
					if (textarea.length == 1) {
						var tr = textarea.closest('tr');

						//The task is not marked?
						if (tr.find('.super.pomodoro-mark').length == 0) {
							tr.find('.grid-cell.grid_cell_assignee').append('' +
								'' +
									'' +
								'' +
							'');
						}
					}
				}
			}
		}
	},

	Pomodoro: {
		current: {
			projectId: null,
			running: false,
			startedAt: 0,
			taskId: null,
		},

		//The second interval for pomodoro counting
		interval: null,

		addPomodoroPanel: function() {
			Zepto('#header').after('' +
				'
' +
					'
00:00:00
' +
				'
' +
			'');
		},

		/**
		 * Start a Pomodoro
		 */
		start: function() {
			var self = this;

			if (! this.current.running) {
				//Call the tick function, the "this" will be the Pomodoro
				this.interval = setInterval(function() {
					self.tick.call(self);
				}, 1000);

				this.current.startedAt = new Date().getTime();
			}
		},

		/**
		 * Start a Pomodoro for a Task
		 * @param  {int} projectId 	The Project ID
		 * @param  {int} taskId 	The Task ID
		 */
		startForTask: function(projectId, taskId) {
			var pomodoroPanel = Zepto('#super-pomodoro-panel');

			if (pomodoroPanel.length == 0) {
				this.addPomodoroPanel();
			}

			this.projectId = projectId;
			this.taskId = taskId;

			this.start();

			//Mark the task on the list
			SuperAsana.Pages.TaskList.markRunningTasks();
		},

		/**
		 * A clock tick
		 */
		tick: function() {
			var now = new Date().getTime(),
				//In seconds
				totalTime = Math.floor((now - this.current.startedAt) / 1000),
				hours = 0,
				minutes = 0,
				seconds = 0;

			//We have hours!
			if (totalTime > 3600) {
				hours = Math.floor(totalTime / 3600);
				totalTime -= (hours * 3600);
			}

			//We have minutes!
			if (totalTime > 60) {
				minutes = Math.floor(totalTime / 60);
				totalTime -= (minutes * 60);
			}

			seconds = totalTime;

			this.updateClock(hours, minutes, seconds);
		},

		/**
		 * Update the clock from Pomodoro Panel
		 * @param  {int} hours   Hours
		 * @param  {int} minutes Minutes
		 * @param  {int} seconds Seconds
		 */
		updateClock: function(hours, minutes, seconds) {
			var clock = Zepto('#super-pomodoro-panel .clock');

			//Format the time
			if (hours < 10) {
				hours = '0' + hours;
			}

			if (minutes < 10) {
				minutes = '0' + minutes;
			}

			if (seconds < 10) {
				seconds = '0' + seconds;
			}

			clock.html(hours + ':' + minutes + ':' + seconds);
		}
	},

	/**
	 * Initializes the correct page for the current url
	 * @param  {string} url Window pathname
	 */
	route: function(url) {
		// Tasks List URL => '/0/1234567890123/list'
		var listRegex = /\/0\/([0-9]*)\/list/,
			//Task Details URL => '/0/1234567890123/1234567890123'
			detailsRegex = /\/0\/([0-9]*)\/([0-9]*)/;

		if (listRegex.test(url)) {
			SuperAsana.Pages.TaskList.init();
		} else if (detailsRegex.test(url)) {
			SuperAsana.Pages.TaskDetails.init();
		}
	},

	User: {
		/**
		 * Get the current user id
		 * @return {int} Current user id
		 */
		getId: function() {
			//Found on Asana source code - Thank you Asana Team S2
			return window.env.user().global_id;
		}
	}
}

SuperAsana.loadScripts();<|endoftext|>


<commit_before>const PATH_REGEXP = new RegExp([
  '(\\\\.)',
  '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))',
].join('|'), 'g');

export function parse(str) {
  const tokens = [];
  let index = 0;
  let path = '';
  let res;

  /*eslint-disable */
  while ((res = PATH_REGEXP.exec(str)) !== null) {
    let offset = res.index;

    path += str.slice(index, offset);
    index = offset + res[0].length;

    // if not exist path or empty string
    if (path) {
      tokens.push(path);
    }
    path = '';

    const token = {
      name: res[3],
      pattern: '[^/]+?',
    };
    tokens.push(token);
  }
  /*eslint-enable */

  if (index < str.length) {
    path += str.substr(index);
  }
  if (path) {
    tokens.push(path);
  }

  return tokens;
}

export function tokensToRegexp(tokens) {
  let route = '';
  const lastToken = tokens[tokens.length - 1];
  const endsWithSlash = (typeof lastToken === 'string' && /\/$/.test(lastToken));

  tokens.forEach((token) => {
    if (typeof token === 'string') {
      route += token;
    } else {
      let capture = token.pattern;

      capture = '/(' + capture + ')';
      route += capture;
    }
  });
  route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?';
  route += '$';

  return new RegExp('^' + route, 'i');
}

export function pathToRegexp(path) {
  const tokens = parse(path);
  const regexp = tokensToRegexp(tokens);

  regexp.keys = [];
  tokens.forEach((token) => {
    if (typeof token !== 'string') {
      regexp.keys.push(token);
    }
  });

  return regexp;
}

export function _getParams(keys, matches) {
  const params = {};

  if (matches) {
    keys.forEach((key, index) => {
      if (!params[key.name]) {
        params[key.name] = [];
      }
      params[key.name].push(matches[index + 1]);
    });
  }
  return params;
}

export function exec(regexp, path) {
  const matches = regexp.exec(path);
  const params = _getParams(regexp.keys, matches);

  if (!matches) {
    return null;
  }

  matches.params = params;
  return matches;
}
<commit_msg>[WIP] Create Router class<commit_after>const PATH_REGEXP = new RegExp([
  '(\\\\.)',
  '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))',
].join('|'), 'g');

export function parse(str) {
  const tokens = [];
  let index = 0;
  let path = '';
  let res;

  /*eslint-disable */
  while ((res = PATH_REGEXP.exec(str)) !== null) {
    let offset = res.index;

    path += str.slice(index, offset);
    index = offset + res[0].length;

    // if not exist path or empty string
    if (path) {
      tokens.push(path);
    }
    path = '';

    const token = {
      name: res[3],
      pattern: '[^/]+?',
    };
    tokens.push(token);
  }
  /*eslint-enable */

  if (index < str.length) {
    path += str.substr(index);
  }
  if (path) {
    tokens.push(path);
  }

  return tokens;
}

export function tokensToRegexp(tokens) {
  let route = '';
  const lastToken = tokens[tokens.length - 1];
  const endsWithSlash = (typeof lastToken === 'string' && /\/$/.test(lastToken));

  tokens.forEach((token) => {
    if (typeof token === 'string') {
      route += token;
    } else {
      let capture = token.pattern;

      capture = '/(' + capture + ')';
      route += capture;
    }
  });
  route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?';
  route += '$';

  return new RegExp('^' + route, 'i');
}

export function pathToRegexp(path) {
  const tokens = parse(path);
  const regexp = tokensToRegexp(tokens);

  regexp.keys = [];
  tokens.forEach((token) => {
    if (typeof token !== 'string') {
      regexp.keys.push(token);
    }
  });

  return regexp;
}

export function _getParams(keys, matches) {
  const params = {};

  if (matches) {
    keys.forEach((key, index) => {
      if (!params[key.name]) {
        params[key.name] = [];
      }
      params[key.name].push(matches[index + 1]);
    });
  }
  return params;
}

export function exec(regexp, path) {
  const matches = regexp.exec(path);
  const params = _getParams(regexp.keys, matches);

  if (!matches) {
    return null;
  }

  matches.params = params;
  return matches;
}

export default class Router {
  constructor() {
    this._routes = [];
  }

  route(path, callback) {
    let regexp = pathToRegexp(path);
    this._routes.push({ regexp, callback });
    return this;
  }

  emit(path) {
    this._routes.forEach((route) => {
      let matches = exec(route.regexp, path);
      if (matches) {
        route.callback(matches.params);
      }
    });
  }
}

let router = new Router();

router.route('/posts/:id', ({ id }) => {
  console.log(id[0]);
});
router.emit('/post/100');
router.emit('/posts/100');
<|endoftext|>


<commit_before>'use strict'

const promisify = require('promisify-es6')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')

module.exports = (send) => {
  return promisify((opts, callback) => {
    if (typeof (opts) === 'function') {
      callback = opts
      opts = {}
    }

    const verbose = opts.v || opts.verbose

    send({
      path: 'swarm/peers',
      qs: opts
    }, (err, result) => {
      if (err) {
        return callback(err)
      }

      if (result.Strings) {
        // go-ipfs <= 0.4.4
        callback(null, result.Strings.map((p) => {
          const res = {}

          if (verbose) {
            const parts = p.split(' ')
            res.addr = multiaddr(parts[0])
            res.latency = parts[1]
          } else {
            res.addr = multiaddr(p)
          }

          res.peer = PeerId.createFromB58String(
            res.addr.decapsulate('ipfs')
          )

          return res
        }))
      } else if (result.Peers) {
        // go-ipfs >= 0.4.5
        callback(null, result.Peers.map((p) => {
          const res = {
            addr: multiaddr(p.Addr),
            peer: PeerId.createFromB58String(p.Peer),
            muxer: p.Muxer
          }

          if (p.Latency) {
            res.latency = p.Latency
          }

          if (p.Streams) {
            res.streams = p.Streams
          }

          return res
        }))
      }
    })
  })
}
<commit_msg>fix: result.Peers can be null, ensure callback is called<commit_after>'use strict'

const promisify = require('promisify-es6')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')

module.exports = (send) => {
  return promisify((opts, callback) => {
    if (typeof (opts) === 'function') {
      callback = opts
      opts = {}
    }

    const verbose = opts.v || opts.verbose

    send({
      path: 'swarm/peers',
      qs: opts
    }, (err, result) => {
      if (err) {
        return callback(err)
      }

      // go-ipfs <= 0.4.4
      if (result.Strings) {
        return callback(null, result.Strings.map((p) => {
          const res = {}

          if (verbose) {
            const parts = p.split(' ')
            res.addr = multiaddr(parts[0])
            res.latency = parts[1]
          } else {
            res.addr = multiaddr(p)
          }

          res.peer = PeerId.createFromB58String(
            res.addr.decapsulate('ipfs')
          )

          return res
        }))
      }

      // go-ipfs >= 0.4.5
      callback(null, (result.Peers || []).map((p) => {
        const res = {
          addr: multiaddr(p.Addr),
          peer: PeerId.createFromB58String(p.Peer),
          muxer: p.Muxer
        }

        if (p.Latency) {
          res.latency = p.Latency
        }

        if (p.Streams) {
          res.streams = p.Streams
        }

        return res
      }))
    })
  })
}
<|endoftext|>


<commit_before>var extend = require("xtend"),
    path = require('path'),
    join = path.join;

var blanketNode = function (userOptions,cli){

    var fs = require("fs"),
        path = require("path"),
        configPath = process.cwd() + '/package.json',
        existsSync = fs.existsSync || path.existsSync,
        file = existsSync(configPath) ? JSON.parse((fs.readFileSync(configPath, 'utf8')||{})) : null,
        packageConfigs;

    if (file){
        var scripts = file.scripts,
            config = file.config;

        if (scripts && scripts.blanket){
            console.warn("BLANKET-" + path + ": `scripts[\"blanket\"]` is deprecated. Please migrate to `config[\"blanket\"]`.\n");
            packageConfigs = scripts.blanket;
        } else if (config && config.blanket){
            packageConfigs = config.blanket;
        }
    }

    var blanketConfigs = packageConfigs ? extend(packageConfigs,userOptions) : userOptions,

        pattern = blanketConfigs  ?
                          blanketConfigs.pattern :
                          "src",
        blanket = require("./blanket").blanket,
        oldLoader = require.extensions['.js'],
        newLoader;

    function escapeRegExp(str) {
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    }
    if (blanketConfigs){
        var newOptions={};
        Object.keys(blanketConfigs).forEach(function (option) {
            var optionValue = blanketConfigs[option];
            if(option === "data-cover-only" || option === "pattern"){
                newOptions.filter = optionValue;
            }
            if(option === "data-cover-never"){
                newOptions.antifilter = optionValue;
            }
            if (option === "data-cover-loader" || option === "loader"){
                newOptions.loader = optionValue;
            }
            if (option === "data-cover-timeout"){
                newOptions.timeout = optionValue;
            }
            if (option === "onlyCwd" && !!optionValue){
                newOptions.cwdRegex = new RegExp("^" + escapeRegExp(process.cwd()), "i");
            }
            if (option === "data-cover-customVariable"){
                newOptions.customVariable = optionValue;
            }
            if (option === "data-cover-flags"){
                newOptions.order = !optionValue.unordered;
                newOptions.ignoreScriptError = !!optionValue.ignoreError;
                newOptions.autoStart = !!optionValue.autoStart;
                newOptions.branchTracking = !!optionValue.branchTracking;
                newOptions.debug = !!optionValue.debug;
                newOptions.engineOnly = !!optionValue.engineOnly;
            }
            if (option === "data-cover-reporter-options"){
                newOptions.reporter_options = optionValue;
            }
        });
        blanket.options(newOptions);
    }

    //helper functions
    blanket.normalizeBackslashes = function (str) {
        return str.replace(/\\/g, '/');
    };

    blanket.restoreNormalLoader = function () {
      if (!blanket.options("engineOnly")){
        newLoader = require.extensions['.js'];
        require.extensions['.js'] = oldLoader;
      }
    };

    blanket.restoreBlanketLoader = function () {
      if (!blanket.options("engineOnly")){
        require.extensions['.js'] = newLoader;
      }
    };

    //you can pass in a string, a regex, or an array of files
    blanket.matchPattern = function (filename,pattern){
        var cwdRegex = blanket.options("cwdRegex");
        if (cwdRegex && !cwdRegex.test(filename)){
            return false;
        }
        if (typeof pattern === 'string'){
            if (pattern.indexOf("[") === 0){
                    //treat as array
                var pattenArr = pattern.slice(1,pattern.length-1).split(",");
                return pattenArr.some(function(elem){
                    return blanket.matchPattern(filename,blanket.normalizeBackslashes(elem.slice(1,-1)));
                });
            }else if ( pattern.indexOf("//") === 0){
                var ex = pattern.slice(2,pattern.lastIndexOf('/'));
                var mods = pattern.slice(pattern.lastIndexOf('/')+1);
                var regex = new RegExp(ex,mods);
                return regex.test(filename);
            }else{
                return filename.indexOf(blanket.normalizeBackslashes(pattern)) > -1;
            }
        }else if ( pattern instanceof Array ){
            return pattern.some(function(elem){
                return filename.indexOf(blanket.normalizeBackslashes(elem)) > -1;
            });
        }else if (pattern instanceof RegExp){
            return pattern.test(filename);
        }else if (typeof pattern === 'function'){
            return pattern(filename);
        }else{
            throw new Error("Bad file instrument indicator.  Must be a string, regex, function, or array.");
        }
    };
    if (!blanket.options("engineOnly")){
        //instrument js files
        require.extensions['.js'] = function(localModule, filename) {
            var pattern = blanket.options("filter"),
                reporter_options = blanket.options("reporter_options"),
                originalFilename = filename;
            filename = blanket.normalizeBackslashes(filename);

            //we check the never matches first
            var antipattern = _blanket.options("antifilter");
            if (typeof antipattern !== "undefined" &&
                    blanket.matchPattern(filename.replace(/\.js$/,""),antipattern)
                ){
                oldLoader(localModule,filename);
                if (_blanket.options("debug")) {console.log("BLANKET-File will never be instrumented:"+filename);}
            }else if (blanket.matchPattern(filename,pattern)){
                if (_blanket.options("debug")) {console.log("BLANKET-Attempting instrument of:"+filename);}
                var content = fs.readFileSync(filename, 'utf8');
                if (reporter_options && reporter_options.shortnames){
                    filename = filename.replace(process.cwd(),"");
                }

                blanket.instrument({
                    inputFile: content,
                    inputFileName: filename
                },function(instrumented){
                    var baseDirPath = blanket.normalizeBackslashes(path.dirname(filename))+'/.';
                    try{
                        instrumented = instrumented.replace(/require\s*\(\s*("|')\./g,'require($1'+baseDirPath);
                        localModule._compile(instrumented, originalFilename);
                    }
                    catch(err){
                        if (_blanket.options("ignoreScriptError")){
                            //we can continue like normal if
                            //we're ignoring script errors,
                            //but otherwise we don't want
                            //to completeLoad or the error might be
                            //missed.
                            if (_blanket.options("debug")) {console.log("BLANKET-There was an error loading the file:"+filename);}
                            oldLoader(localModule,filename);
                        }else{
                            throw new Error("BLANKET-Error parsing instrumented code: "+err);
                        }
                    }
                });
            }else{
                oldLoader(localModule, originalFilename);
            }
        };
    }
    //if a loader is specified, use it
    if (blanket.options("loader")){
        require(blanket.options("loader"))(blanket);
    }
    newLoader = require.extensions['.js'];
    return blanket;
};

if ((process.env && process.env.BLANKET_COV===1) ||
    (process.ENV && process.ENV.BLANKET_COV)){
    module.exports = blanketNode({engineOnly:true},false);
}else{
    var args = process.argv;
    var blanketRequired = false;

    for (var i = 0; i < args.length; i++) {
        if (['-r', '--require'].indexOf(args[i]) >= 0 &&
            args[i + 1] === 'blanket') {
            blanketRequired = true;
        }
    }

    if (args[0] === 'node' &&
        args[1].indexOf(join('node_modules','mocha','bin')) > -1 &&
        blanketRequired){

        //using mocha cli
        module.exports = blanketNode(null,true);
    }else{
        //not mocha cli
        module.exports = function(options){
            //we don't want to expose the cli option.
            return blanketNode(options,false);
        };
    }
}

<commit_msg>Resolved jeremieca's problem in issue #293.<commit_after>var extend = require("xtend"),
    path = require('path'),
    join = path.join;

var blanketNode = function (userOptions,cli){

    var fs = require("fs"),
        path = require("path"),
        configPath = process.cwd() + '/package.json',
        existsSync = fs.existsSync || path.existsSync,
        file = existsSync(configPath) ? JSON.parse((fs.readFileSync(configPath, 'utf8')||{})) : null,
        packageConfigs;

    if (file){
        var scripts = file.scripts,
            config = file.config;

        if (scripts && scripts.blanket){
            console.warn("BLANKET-" + path + ": `scripts[\"blanket\"]` is deprecated. Please migrate to `config[\"blanket\"]`.\n");
            packageConfigs = scripts.blanket;
        } else if (config && config.blanket){
            packageConfigs = config.blanket;
        }
    }

    var blanketConfigs = packageConfigs ? extend(packageConfigs,userOptions) : userOptions,

        pattern = blanketConfigs  ?
                          blanketConfigs.pattern :
                          "src",
        blanket = require("./blanket").blanket,
        oldLoader = require.extensions['.js'],
        newLoader;

    function escapeRegExp(str) {
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    }
    if (blanketConfigs){
        var newOptions={};
        Object.keys(blanketConfigs).forEach(function (option) {
            var optionValue = blanketConfigs[option];
            if(option === "data-cover-only" || option === "pattern"){
                newOptions.filter = optionValue;
            }
            if(option === "data-cover-never"){
                newOptions.antifilter = optionValue;
            }
            if (option === "data-cover-loader" || option === "loader"){
                newOptions.loader = optionValue;
            }
            if (option === "data-cover-timeout"){
                newOptions.timeout = optionValue;
            }
            if (option === "onlyCwd" && !!optionValue){
                newOptions.cwdRegex = new RegExp("^" + escapeRegExp(process.cwd()), "i");
            }
            if (option === "data-cover-customVariable"){
                newOptions.customVariable = optionValue;
            }
            if (option === "data-cover-flags"){
                newOptions.order = !optionValue.unordered;
                newOptions.ignoreScriptError = !!optionValue.ignoreError;
                newOptions.autoStart = !!optionValue.autoStart;
                newOptions.branchTracking = !!optionValue.branchTracking;
                newOptions.debug = !!optionValue.debug;
                newOptions.engineOnly = !!optionValue.engineOnly;
            }
            if (option === "data-cover-reporter-options"){
                newOptions.reporter_options = optionValue;
            }
        });
        blanket.options(newOptions);
    }

    //helper functions
    blanket.normalizeBackslashes = function (str) {
        return str.replace(/\\/g, '/');
    };

    blanket.restoreNormalLoader = function () {
      if (!blanket.options("engineOnly")){
        newLoader = require.extensions['.js'];
        require.extensions['.js'] = oldLoader;
      }
    };

    blanket.restoreBlanketLoader = function () {
      if (!blanket.options("engineOnly")){
        require.extensions['.js'] = newLoader;
      }
    };

    //you can pass in a string, a regex, or an array of files
    blanket.matchPattern = function (filename,pattern){
        var cwdRegex = blanket.options("cwdRegex");
        if (cwdRegex && !cwdRegex.test(filename)){
            return false;
        }
        if (typeof pattern === 'string'){
            if (pattern.indexOf("[") === 0){
                    //treat as array
                var pattenArr = pattern.slice(1,pattern.length-1).split(",");
                return pattenArr.some(function(elem){
                    return blanket.matchPattern(filename,blanket.normalizeBackslashes(elem.slice(1,-1)));
                });
            }else if ( pattern.indexOf("//") === 0){
                var ex = pattern.slice(2,pattern.lastIndexOf('/'));
                var mods = pattern.slice(pattern.lastIndexOf('/')+1);
                var regex = new RegExp(ex,mods);
                return regex.test(filename);
            }else{
                return filename.indexOf(blanket.normalizeBackslashes(pattern)) > -1;
            }
        }else if ( pattern instanceof Array ){
            return pattern.some(function(elem){
                return filename.indexOf(blanket.normalizeBackslashes(elem)) > -1;
            });
        }else if (pattern instanceof RegExp){
            return pattern.test(filename);
        }else if (typeof pattern === 'function'){
            return pattern(filename);
        }else{
            throw new Error("Bad file instrument indicator.  Must be a string, regex, function, or array.");
        }
    };
    if (!blanket.options("engineOnly")){
        //instrument js files
        require.extensions['.js'] = function(localModule, filename) {
            var pattern = blanket.options("filter"),
                reporter_options = blanket.options("reporter_options"),
                originalFilename = filename,
                inputFilename = filename;
            filename = blanket.normalizeBackslashes(filename);

            //we check the never matches first
            var antipattern = _blanket.options("antifilter");
            if (typeof antipattern !== "undefined" &&
                    blanket.matchPattern(filename.replace(/\.js$/,""),antipattern)
                ){
                oldLoader(localModule,filename);
                if (_blanket.options("debug")) {console.log("BLANKET-File will never be instrumented:"+filename);}
            }else if (blanket.matchPattern(filename,pattern)){
                if (_blanket.options("debug")) {console.log("BLANKET-Attempting instrument of:"+filename);}
                var content = fs.readFileSync(filename, 'utf8');
                if (reporter_options && reporter_options.shortnames){
                    inputFilename = filename.replace(path.dirname(filename),"");
                }

                blanket.instrument({
                    inputFile: content,
                    inputFileName: inputFilename
                },function(instrumented){
                    var baseDirPath = blanket.normalizeBackslashes(path.dirname(filename))+'/.';
                    try{
                        instrumented = instrumented.replace(/require\s*\(\s*("|')\./g,'require($1'+baseDirPath);
                        localModule._compile(instrumented, originalFilename);
                    }
                    catch(err){
                        if (_blanket.options("ignoreScriptError")){
                            //we can continue like normal if
                            //we're ignoring script errors,
                            //but otherwise we don't want
                            //to completeLoad or the error might be
                            //missed.
                            if (_blanket.options("debug")) {console.log("BLANKET-There was an error loading the file:"+filename);}
                            oldLoader(localModule,filename);
                        }else{
                            throw new Error("BLANKET-Error parsing instrumented code: "+err);
                        }
                    }
                });
            }else{
                oldLoader(localModule, originalFilename);
            }
        };
    }
    //if a loader is specified, use it
    if (blanket.options("loader")){
        require(blanket.options("loader"))(blanket);
    }
    newLoader = require.extensions['.js'];
    return blanket;
};

if ((process.env && process.env.BLANKET_COV===1) ||
    (process.ENV && process.ENV.BLANKET_COV)){
    module.exports = blanketNode({engineOnly:true},false);
}else{
    var args = process.argv;
    var blanketRequired = false;

    for (var i = 0; i < args.length; i++) {
        if (['-r', '--require'].indexOf(args[i]) >= 0 &&
            args[i + 1] === 'blanket') {
            blanketRequired = true;
        }
    }

    if (args[0] === 'node' &&
        args[1].indexOf(join('node_modules','mocha','bin')) > -1 &&
        blanketRequired){

        //using mocha cli
        module.exports = blanketNode(null,true);
    }else{
        //not mocha cli
        module.exports = function(options){
            //we don't want to expose the cli option.
            return blanketNode(options,false);
        };
    }
}

<|endoftext|>


<commit_before>const Wappalyzer = require('wappalyzer');
const Tech = require('./tech');
const request = require('request-promise');
const requestErrors = require('request-promise/errors');
const Version = require('./version');
const ScanResult = require('./scanResult');

const PROGRESS_STEPS = {
  init: {
    start: 0,
    end: 1,
    defaultDescription: 'Starting up...'
  },
  fetch: {
    start: 1,
    end: 5,
    defaultDescription: 'Fetching page content'
  },
  analyze: {
    start: 5,
    end: 10,
    defaultDescription: 'Analyzing page content'
  },
  deepscan: {
    start: 10,
    end: 100,
    defaultDescription: 'Fine grain analysis'
  },
  complete: {
    start: 100,
    end: 100,
    defaultDescription: 'Scan complete'
  }
}

const DEEPSCAN_RANGE_PHASE_1 = 100 / 3 // phase 1 : looking for a hidden CMS or CMS version
const DEEPSCAN_RANGE_PHASE_2 = 100 - DEEPSCAN_RANGE_PHASE_1 // phase 2 : looking for plugins

/**
 * @summary Scan a website
 */
class Scanner {
  /**
   * @constructor
   * @summary Scan a website
   * @param {String} url - the url
   * @param {Function} progressCallback - the progress callback
   * @param {String} [homepageBody] - the homepage body
   * @returns {undefined} void
   */
  constructor(url, progressCallback, homepageBody) {
    this.url = url;
    this.progressCallback = progressCallback;
    this.homepageBody = homepageBody;
    this.httpStatus = null;
    this.networkError = null;
    this.apps = null;
  }

  /**
   * @summary Start the scan
   * @returns {undefined} void
   */
  async start() {
    this.scanDate = this.getCurrentDate();

    if (!await this.checkHttpStatus()) {
      // stop on error
      this.reportProgress('init', 100);
      return Promise.reject(new Error('unreachable URL'));
    };

    await this.wappalyze();
    await this.deepScan();

    // end
    const completeReport = this.reportProgress('complete', 100, 'Scan complete');
    return Promise.resolve(completeReport);
  }

  /**
   * @summary Get the current date
   * @returns {Date} Current date
   */
  getCurrentDate() {
    return new Date();
  }

  /**
   * @summary Check if the URL is reachable & update httpStatus and homepageBody if necessary
   * @returns {Boolean} True if the URL is reachable
   */
  async checkHttpStatus() {
    const requestOptions = Tech.getReqOptions(this.url);
    requestOptions.resolveWithFullResponse = true;

    try {
      const response = await request(requestOptions);
      this.httpStatus = response.statusCode;

      // update homepagebody if necessary
      this.homepageBody = this.homepageBody || response.body;
      return true;
    } catch (err) {
      if (err instanceof requestErrors.StatusCodeError) {
        this.httpStatus = err.statusCode;
      } else {
        this.networkError = err.cause && err.cause.code === 'ENOTFOUND' ? 'DNS ERROR' : 'UNKOWN ERROR';
      }

      return false;
    }
  }

  /**
   * @summary Launch Wappalyzer
   * @returns {undefined} void
   */
  async wappalyze() {
    const options = {
      debug: false,
      delay: 500,
      maxDepth: 1,
      maxUrls: 1,
      maxWait: 5000,
      recursive: false,
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
    };

    const wappalyzer = new Wappalyzer(this.url, options);
    wappalyzer.log = (message) => {
      if (message.indexOf(' fetch;') !== -1) {
        this.reportProgress('fetch', 0);
      }
      if (message.indexOf('browser.visit start') !== -1) {
        this.reportProgress('analyze', 0);
      }
    }

    this.apps = await wappalyzer.analyze()
  }

  /**
   * @summary Launch Deepscan
   * @returns {undefined} void
   */
  async deepScan() {
    this.initializeDeepScan();

    for (const app of this.techApps) {
      try {
        let tech = new Tech(app.name);
        tech.loadVersions();
        tech.findRoots(this.url, this.homepageBody)

        if (!app.version) {
          await this.deepScanApp(tech, app);
        }

        await this.findPlugins(tech, app);
      } catch (err) {
        // app not found, go to next app
      }
    }
  }

  /**
   * @summary Initialize the deepScan.
   * @returns {undefined} void
   */
  initializeDeepScan() {
    let hasCMS = false;
    this.techApps = [];
    this.deepScanProgress = 0;

    for (const app of this.apps.applications) {
      if (Tech.allTechs.indexOf(app.name) !== -1) {
        this.techApps.push(app);
      }

      const cats = [];
      // look for a CMS
      for (const categories of app.categories) {
        for (const cat in categories) {
          cats.push(cat);
          if (cat === '1' || cat === '11' || cat === '6') { // CMS, blog ou ecommerce
            hasCMS = true;
          }
        }
      }

      // update the array format to simplify PHP exploitation : [{"1": "CMS"}, {"11": "Blogs"}] => [1, 11]
      app.categories = cats;
    }

    if (!hasCMS) {
      this.addTechApp('WordPress', 100, 'WordPress.svg', 'http://wordpress.org', [1, 11]); // {"1": "CMS"},{"11": "Blogs"}
      this.addTechApp('Drupal', 100, 'Drupal.png', 'http://drupal.org', [1]); // {"1": "CMS"}
    }
  }

  /**
   * @summary Add an application to techApps
   * @param {String} name - app name
   * @param {Number} confidence - app confidence
   * @param {String} icon - app icon
   * @param {String} website - app website
   * @param {Array<Number>} categories - app categories
   * @returns {undefined} void
   */
  addTechApp(name, confidence, icon, website, categories) {
    const app = {
      name,
      confidence,
      icon,
      website,
      categories
    };

    this.techApps.push(app)
  }

  /**
   * @async
   * @summary Launch a deepScan on specified app
   * @param {Tech} tech - the tech object
   * @param {Object} app - the app to scan
   * @returns {Promise} A promise that resolve when the scan is finished
   */
  async deepScanApp(tech, app) {
    return new Promise((resolve, reject) => {
      const hiddenApp = !this.apps.applications.find((application) => {
        return application.name === app.name;
      })

      let progressMessage;
      if (hiddenApp) {
        progressMessage = 'Looking for hidden ' + app.name + '.';
      } else {
        progressMessage = 'Looking for ' + app.name + ' version.';
      }

      const progressCallback = (progress) => {
        const p = this.deepScanProgress + (DEEPSCAN_RANGE_PHASE_1 / 100) * progress / this.techApps.length;
        this.reportProgress('deepscan', p, progressMessage);
      }

      const resultCallback = (err, result) => {
        this.deepScanProgress += DEEPSCAN_RANGE_PHASE_1 / this.techApps.length
        this.reportProgress('deepscan', this.deepScanProgress, progressMessage);

        if (result.status === 'fail') {
          reject(err);
          return;
        }

        if (hiddenApp) {
          this.apps.applications.push(app);
        }

        this.setDetected(app, {version: result.versions, regex: '.*'}, 'file', result.proofs);
        resolve();
      }

      tech.deepScan(resultCallback, progressCallback);
    })
  }

  /**
   * @summary Set app as detected
   * @param {Object} app - app
   * @param {Object} pattern - pattern
   * @param {String} type - type
   * @param {Array} proofs - proofs
   * @returns {undefined} void
   */
  setDetected(app, pattern, type, proofs) {
    app.confidence = {};
    for (const proof of proofs) {
      const uri = proof.root + '/' + proof.path;
      app.confidence[type + ' ' + uri] = proof.status;
    }

    // get most probable version among pattern.version. pattern.version is an array
    let maxVersion = null;
    let l = pattern.version.length;
    while (l--) { // take max release version, unless no release version detected.
      if (!maxVersion) {
        maxVersion = pattern.version[l];
      } else {
        const v = pattern.version[l];
        var v1 = new Version(maxVersion);
        var v2 = new Version(v);
        if (v1.isReleaseVersion() === true) {
          if (v2.isReleaseVersion() === true) {
            if (v2.GT(v1)) {
              maxVersion = v;
            }
          }
        } else {
          if (v2.isReleaseVersion() === true) {
            maxVersion = v;
          } else {
            if (v2.GT(v1)) {
              maxVersion = v;
            }
          }
        }
      }
    }

    app.version = maxVersion;
    app.versions = pattern.version; // unlike other types, these are _possible_ versions. only one is really there.
  }

  /**
   * @summary Find plugins for specified app
   * @param {Tech} tech - the tech object
   * @param {Object} app - the app
   * @returns {undefined} void
   */
  async findPlugins(tech, app) {
    return new Promise((resolve) => {
      const progressCallback = (detectedPlugins, pluginProgress) => {
        let p = this.deepScanProgress + (DEEPSCAN_RANGE_PHASE_2 / 100) * pluginProgress / this.techApps.length;
        app.plugins = detectedPlugins;
        this.reportProgress('deepscan', p, 'Looking for ' + app.name + ' plugins.');
      };

      const resultCallback = (detectedPlugins) => {
        this.deepScanProgress += DEEPSCAN_RANGE_PHASE_2 / this.techApps.length;

        app.plugins = detectedPlugins
        this.reportProgress('deepscan', this.deepScanProgress, 'Looking for ' + app.name + ' plugins.');
        resolve();
      };

      tech.findPlugins(app.version, progressCallback, resultCallback);
    });
  }

  /**
   * @summary Report progress
   * @param {String} status - status
   * @param {Number} inStepProgress - in step progress
   * @param {String} descriptionOverride - description override
   * @returns {Object} The progress object
   */
  reportProgress(status, inStepProgress, descriptionOverride) {
    const step = PROGRESS_STEPS[status];
    const progressDescription = descriptionOverride || step.defaultDescription;
    const progress = step.start + (((step.end - step.start) * inStepProgress) / 100);

    const scanResult = new ScanResult(
      this.url,
      status,
      progress,
      progressDescription,
      this.scanDate,
      this.getCurrentDate(),
      this.networkError,
      this.httpStatus,
      this.apps && this.apps.applications
    );

    if (this.progressCallback) {
      this.progressCallback(scanResult)
    }

    return scanResult
  }
}

module.exports = Scanner<commit_msg>Add proofs to detected app<commit_after>const Wappalyzer = require('wappalyzer');
const Tech = require('./tech');
const request = require('request-promise');
const requestErrors = require('request-promise/errors');
const Version = require('./version');
const ScanResult = require('./scanResult');

const PROGRESS_STEPS = {
  init: {
    start: 0,
    end: 1,
    defaultDescription: 'Starting up...'
  },
  fetch: {
    start: 1,
    end: 5,
    defaultDescription: 'Fetching page content'
  },
  analyze: {
    start: 5,
    end: 10,
    defaultDescription: 'Analyzing page content'
  },
  deepscan: {
    start: 10,
    end: 100,
    defaultDescription: 'Fine grain analysis'
  },
  complete: {
    start: 100,
    end: 100,
    defaultDescription: 'Scan complete'
  }
}

const DEEPSCAN_RANGE_PHASE_1 = 100 / 3 // phase 1 : looking for a hidden CMS or CMS version
const DEEPSCAN_RANGE_PHASE_2 = 100 - DEEPSCAN_RANGE_PHASE_1 // phase 2 : looking for plugins

/**
 * @summary Scan a website
 */
class Scanner {
  /**
   * @constructor
   * @summary Scan a website
   * @param {String} url - the url
   * @param {Function} progressCallback - the progress callback
   * @param {String} [homepageBody] - the homepage body
   * @returns {undefined} void
   */
  constructor(url, progressCallback, homepageBody) {
    this.url = url;
    this.progressCallback = progressCallback;
    this.homepageBody = homepageBody;
    this.httpStatus = null;
    this.networkError = null;
    this.apps = null;
  }

  /**
   * @summary Start the scan
   * @returns {undefined} void
   */
  async start() {
    this.scanDate = this.getCurrentDate();

    if (!await this.checkHttpStatus()) {
      // stop on error
      this.reportProgress('init', 100);
      return Promise.reject(new Error('unreachable URL'));
    };

    await this.wappalyze();
    await this.deepScan();

    // end
    const completeReport = this.reportProgress('complete', 100, 'Scan complete');
    return Promise.resolve(completeReport);
  }

  /**
   * @summary Get the current date
   * @returns {Date} Current date
   */
  getCurrentDate() {
    return new Date();
  }

  /**
   * @summary Check if the URL is reachable & update httpStatus and homepageBody if necessary
   * @returns {Boolean} True if the URL is reachable
   */
  async checkHttpStatus() {
    const requestOptions = Tech.getReqOptions(this.url);
    requestOptions.resolveWithFullResponse = true;

    try {
      const response = await request(requestOptions);
      this.httpStatus = response.statusCode;

      // update homepagebody if necessary
      this.homepageBody = this.homepageBody || response.body;
      return true;
    } catch (err) {
      if (err instanceof requestErrors.StatusCodeError) {
        this.httpStatus = err.statusCode;
      } else {
        this.networkError = err.cause && err.cause.code === 'ENOTFOUND' ? 'DNS ERROR' : 'UNKOWN ERROR';
      }

      return false;
    }
  }

  /**
   * @summary Launch Wappalyzer
   * @returns {undefined} void
   */
  async wappalyze() {
    const options = {
      debug: false,
      delay: 500,
      maxDepth: 1,
      maxUrls: 1,
      maxWait: 5000,
      recursive: false,
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
    };

    const wappalyzer = new Wappalyzer(this.url, options);
    wappalyzer.log = (message) => {
      if (message.indexOf(' fetch;') !== -1) {
        this.reportProgress('fetch', 0);
      }
      if (message.indexOf('browser.visit start') !== -1) {
        this.reportProgress('analyze', 0);
      }
    }

    this.apps = await wappalyzer.analyze()
  }

  /**
   * @summary Launch Deepscan
   * @returns {undefined} void
   */
  async deepScan() {
    this.initializeDeepScan();

    for (const app of this.techApps) {
      try {
        let tech = new Tech(app.name);
        tech.loadVersions();
        tech.findRoots(this.url, this.homepageBody)

        if (!app.version) {
          await this.deepScanApp(tech, app);
        }

        await this.findPlugins(tech, app);
      } catch (err) {
        // app not found, go to next app
      }
    }
  }

  /**
   * @summary Initialize the deepScan.
   * @returns {undefined} void
   */
  initializeDeepScan() {
    let hasCMS = false;
    this.techApps = [];
    this.deepScanProgress = 0;

    for (const app of this.apps.applications) {
      if (Tech.allTechs.indexOf(app.name) !== -1) {
        this.techApps.push(app);
      }

      const cats = [];
      // look for a CMS
      for (const categories of app.categories) {
        for (const cat in categories) {
          cats.push(cat);
          if (cat === '1' || cat === '11' || cat === '6') { // CMS, blog ou ecommerce
            hasCMS = true;
          }
        }
      }

      // update the array format to simplify PHP exploitation : [{"1": "CMS"}, {"11": "Blogs"}] => [1, 11]
      app.categories = cats;
    }

    if (!hasCMS) {
      this.addTechApp('WordPress', 100, 'WordPress.svg', 'http://wordpress.org', [1, 11]); // {"1": "CMS"},{"11": "Blogs"}
      this.addTechApp('Drupal', 100, 'Drupal.png', 'http://drupal.org', [1]); // {"1": "CMS"}
    }
  }

  /**
   * @summary Add an application to techApps
   * @param {String} name - app name
   * @param {Number} confidence - app confidence
   * @param {String} icon - app icon
   * @param {String} website - app website
   * @param {Array<Number>} categories - app categories
   * @returns {undefined} void
   */
  addTechApp(name, confidence, icon, website, categories) {
    const app = {
      name,
      confidence,
      icon,
      website,
      categories
    };

    this.techApps.push(app)
  }

  /**
   * @async
   * @summary Launch a deepScan on specified app
   * @param {Tech} tech - the tech object
   * @param {Object} app - the app to scan
   * @returns {Promise} A promise that resolve when the scan is finished
   */
  async deepScanApp(tech, app) {
    return new Promise((resolve, reject) => {
      const hiddenApp = !this.apps.applications.find((application) => {
        return application.name === app.name;
      })

      let progressMessage;
      if (hiddenApp) {
        progressMessage = 'Looking for hidden ' + app.name + '.';
      } else {
        progressMessage = 'Looking for ' + app.name + ' version.';
      }

      const progressCallback = (progress) => {
        const p = this.deepScanProgress + (DEEPSCAN_RANGE_PHASE_1 / 100) * progress / this.techApps.length;
        this.reportProgress('deepscan', p, progressMessage);
      }

      const resultCallback = (err, result) => {
        this.deepScanProgress += DEEPSCAN_RANGE_PHASE_1 / this.techApps.length
        this.reportProgress('deepscan', this.deepScanProgress, progressMessage);

        if (result.status === 'fail') {
          reject(err);
          return;
        }

        if (hiddenApp) {
          this.apps.applications.push(app);
        }

        this.setDetected(app, {version: result.versions, regex: '.*'}, 'file', result.proofs);
        resolve();
      }

      tech.deepScan(resultCallback, progressCallback);
    })
  }

  /**
   * @summary Set app as detected
   * @param {Object} app - app
   * @param {Object} pattern - pattern
   * @param {String} type - type
   * @param {Array} proofs - proofs
   * @returns {undefined} void
   */
  setDetected(app, pattern, type, proofs) {
    app.proofs = proofs;

    // get most probable version among pattern.version. pattern.version is an array
    let maxVersion = null;
    let l = pattern.version.length;
    while (l--) { // take max release version, unless no release version detected.
      if (!maxVersion) {
        maxVersion = pattern.version[l];
      } else {
        const v = pattern.version[l];
        var v1 = new Version(maxVersion);
        var v2 = new Version(v);
        if (v1.isReleaseVersion() === true) {
          if (v2.isReleaseVersion() === true) {
            if (v2.GT(v1)) {
              maxVersion = v;
            }
          }
        } else {
          if (v2.isReleaseVersion() === true) {
            maxVersion = v;
          } else {
            if (v2.GT(v1)) {
              maxVersion = v;
            }
          }
        }
      }
    }

    app.version = maxVersion;
    app.versions = pattern.version; // unlike other types, these are _possible_ versions. only one is really there.
  }

  /**
   * @summary Find plugins for specified app
   * @param {Tech} tech - the tech object
   * @param {Object} app - the app
   * @returns {undefined} void
   */
  async findPlugins(tech, app) {
    return new Promise((resolve) => {
      const progressCallback = (detectedPlugins, pluginProgress) => {
        let p = this.deepScanProgress + (DEEPSCAN_RANGE_PHASE_2 / 100) * pluginProgress / this.techApps.length;
        app.plugins = detectedPlugins;
        this.reportProgress('deepscan', p, 'Looking for ' + app.name + ' plugins.');
      };

      const resultCallback = (detectedPlugins) => {
        this.deepScanProgress += DEEPSCAN_RANGE_PHASE_2 / this.techApps.length;

        app.plugins = detectedPlugins
        this.reportProgress('deepscan', this.deepScanProgress, 'Looking for ' + app.name + ' plugins.');
        resolve();
      };

      tech.findPlugins(app.version, progressCallback, resultCallback);
    });
  }

  /**
   * @summary Report progress
   * @param {String} status - status
   * @param {Number} inStepProgress - in step progress
   * @param {String} descriptionOverride - description override
   * @returns {Object} The progress object
   */
  reportProgress(status, inStepProgress, descriptionOverride) {
    const step = PROGRESS_STEPS[status];
    const progressDescription = descriptionOverride || step.defaultDescription;
    const progress = step.start + (((step.end - step.start) * inStepProgress) / 100);

    const scanResult = new ScanResult(
      this.url,
      status,
      progress,
      progressDescription,
      this.scanDate,
      this.getCurrentDate(),
      this.networkError,
      this.httpStatus,
      this.apps && this.apps.applications
    );

    if (this.progressCallback) {
      this.progressCallback(scanResult)
    }

    return scanResult
  }
}

module.exports = Scanner<|endoftext|>


<commit_before>/*! Task Slayer | (c) 2014 Eric Mountain | http://github.com/... */

define(["jquery", "angular", "angular-route", "angular-animate", "app/schema", "app/couchstorage", "app/localstorage", "app/datastorage"], function($) {

	// FIXME wrap in $(function() { ? c.f. https://github.com/requirejs/example-jquery-cdn/blob/master/www/js/app/main.js (doesn't seem necessary)

//	$(function() {
		// Handle resizing
		function resizeSubBlocks() {
			var marginPct = 0;
			var pageHeight = $(window).height();
			var marginHeight = pageHeight * marginPct / 100;
			var usableHeight = pageHeight - marginHeight * 2;

			// Make sub-blocks half the size of the page
			var subBlockHeight = usableHeight / 2;
			$(".sub-block").css({"height": subBlockHeight});
		}

		$(window).resize(function() {
			resizeSubBlocks();
		});

		resizeSubBlocks();

		// Bootstrap Angular
		var taskSlayerApp = angular.module('taskSlayerApp', ['ngRoute', 'ngAnimate', 'localstorage', 'couchstorage', 'datastorage', 'schema']);

		taskSlayerApp.factory('dataModelService', ['$rootScope', 'datastorage', function ($rootScope, datastorage) {

			var service = {

				keyBase: undefined,

				model: {},

				SaveState: function () {
					datastorage.save(service.model, service.RestoreState);
				},

				RestoreState: function (event, args) {
					if (typeof keyBase === 'undefined')
						keyBase = "TaskSlayer-" + args.location;

					datastorage.load(keyBase, function(data) {
						service.model = data;

						$rootScope.$broadcast("staterestored", args);
					});
				}
			}

			$rootScope.$on("savestate", service.SaveState);
			$rootScope.$on("restorestate", service.RestoreState);

			return service;
		}]);

		taskSlayerApp.config(['$routeProvider', function($routeProvider) {
			$routeProvider.when('', { controller: 'taskSlayerCtrl'})
				.otherwise({ controller: 'taskSlayerCtrl'});
		}]);

		taskSlayerApp.controller('taskSlayerCtrl', ['$scope', '$rootScope', '$route', '$timeout', '$location', 'dataModelService', function($scope, $rootScope, $route, $timeout, $location, dataModelService) {

			$scope.message = "";
			$scope.showMessage = false;

			$scope.dataModelService = dataModelService;
			$scope.categories = dataModelService.model.taskCategories;

			$scope.change = function() {
				$rootScope.$broadcast('savestate');
			};

			$scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) {
				var loc = $location.absUrl().replace(/[/:]/g, "-");
				$rootScope.$broadcast('restorestate', {location: loc});
			});

			$scope.$on("staterestored", function(event, args) {
				args = (typeof args === "undefined") ? {} : args;

				// Update shortcut after state has been restored asynchronously
				$scope.categories = dataModelService.model.taskCategories;

				console.log("State restored event");
				console.log(args);
				if (args.message) {
					// Let the display stabilise before displaying the message
					$timeout(function() {
						$scope.message = args.message;
						$scope.showMessage = true;

						$timeout(function() {
							$scope.showMessage = false;
						}, 2500);
					}, 1000);
				}
			});

			$scope.addTask = function(category, task, persist) {
				task = (typeof task === "undefined") ? {description: "", done: false} : task;
				persist = (typeof persist === "undefined") ? true : persist;

				category.tasks.list.push(task);

				if (persist)
					$rootScope.$broadcast('savestate');
			};

			$scope.addTaskAfterIndex = function(category, index) {
				category.tasks.list.splice(index + 1, 0, {description: "", done: false});
				$rootScope.$broadcast('savestate');
			};

			$scope.deleteTask = function(category, index, persist) {
				persist = (typeof persist === "undefined") ? true : persist;

				task = category.tasks.list.splice(index, 1)[0];

				var target;

				if (index == category.tasks.list.length && index > 0) {
					target = index - 1;
				} else {
					target = index;
				}

				$scope.focusTask(category, target);

				if (persist)
					$rootScope.$broadcast('savestate');

				return task;
			};

			$scope.focusTask = function(category, index) {
				if (category.tasks.list.length > index && index >= 0) {
					setTimeout(function() {
						$("#" + category.description + "-" + index).focus();
					}, 0);
				}
			};

			$scope.moveTask = function(category, index, direction) {
				var target = index + direction;

				if (target < 0 || target >= category.tasks.list.length) {
					return;
				}

				var tmp = category.tasks.list[index];
				category.tasks.list[index] = category.tasks.list[target];
				category.tasks.list[target] = tmp;

				$rootScope.$broadcast('savestate');

				$scope.focusTask(category, target);
			};

			$scope.moveTaskToCategory = function(destination, source, index) {
				if (destination === source)
					return;

				task = $scope.deleteTask(source, index, false);

				$scope.addTask(destination, task);
			};

			$scope.focusCategory = function(category) {
				if (category.tasks.list.length == 0)
					$scope.addTask(category);
				else
					$scope.focusTask(category, 0);
			}

			$scope.keypress = function($event) {
				var isHandledHere = true;

				if ($event.altKey && !$event.shiftKey) {
					switch($event.keyCode) {
					case 78: // N
						$scope.focusCategory($scope.categories.urgentImportant);
						break;
					case 83: // S
						$scope.focusCategory($scope.categories.important);
						break;
					case 68: // D
						$scope.focusCategory($scope.categories.urgent);
						break;
					case 87: // W
						$scope.focusCategory($scope.categories.waste);
						break;
					default:
						isHandledHere = false;
					}
				} else
					isHandledHere = false;

				if (isHandledHere)
					$event.preventDefault();
			};

			$scope.taskKeypress = function($event, category, index) {
				var isHandledHere = true;

				if ($event.altKey && $event.shiftKey) {
					switch($event.keyCode) {
					case 78: // N
						$scope.moveTaskToCategory($scope.categories.urgentImportant, category, index);
						break;
					case 83: // S
						$scope.moveTaskToCategory($scope.categories.important, category, index);
						break;
					case 68: // D
						$scope.moveTaskToCategory($scope.categories.urgent, category, index);
						break;
					case 87: // W
						$scope.moveTaskToCategory($scope.categories.waste, category, index);
						break;
					default:
						isHandledHere = false;
					}
				} else if ($event.ctrlKey) {
					switch($event.keyCode) {
					case 45: // Insert
						$scope.addTask(category);
						break;
					case 8: // Backspace
					case 46: // Delete
					case 13: // Enter/Return
						$scope.deleteTask(category, index);
						break;
					case 38: // Up
						$scope.moveTask(category, index, -1);
						break;
					case 40: // Down
						$scope.moveTask(category, index, 1);
						break;
					default:
						isHandledHere = false;
					}
				} else if (!$event.ctrlKey && !$event.altKey) {
					switch($event.keyCode) {
					case 13: // Enter/Return
						$scope.addTaskAfterIndex(category, index);
						break;
					case 38: // Up
						$scope.focusTask(category, index - 1);
						break;
					case 40: // Down
						$scope.focusTask(category, index + 1);
						break;
					default:
						isHandledHere = false;
					}
				}

				if (isHandledHere)
					$event.preventDefault();
			};
		}]);

		taskSlayerApp.directive('taskCategory', function() {
			return {
				restrict: 'E',
				scope: {
					category: '=',
					deleteTask: '&',
					addTask: '&',
					change: '&',
					taskKeypress: '&',
					title: '@'
                },
				templateUrl: 'angular-templates/task-category.html'
			};
		});

		taskSlayerApp.directive('initialFocus', function() {
			var timer;

			return function(scope, element, attr) {
				if (timer) clearTimeout(timer);

				timer = setTimeout(function() {
					element[0].focus();
				}, 0);
			}
		});

		angular.bootstrap(document, ['taskSlayerApp']);

		// Hide the loading pane...
		$("#wait-pane-master").css({visibility: "hidden"});

//	});

});

<commit_msg>Cosmetic.<commit_after>/*! Task Slayer | (c) 2014 Eric Mountain | http://github.com/... */

define(["jquery", "angular", "angular-route", "angular-animate", "app/schema", "app/couchstorage", "app/localstorage", "app/datastorage"], function($) {

	// FIXME wrap in $(function() { ? c.f. https://github.com/requirejs/example-jquery-cdn/blob/master/www/js/app/main.js (doesn't seem necessary)

//	$(function() {
		// Handle resizing
		function resizeSubBlocks() {
			var marginPct = 0;
			var pageHeight = $(window).height();
			var marginHeight = pageHeight * marginPct / 100;
			var usableHeight = pageHeight - marginHeight * 2;

			// Make sub-blocks half the size of the page
			var subBlockHeight = usableHeight / 2;
			$(".sub-block").css({"height": subBlockHeight});
		}

		$(window).resize(function() {
			resizeSubBlocks();
		});

		resizeSubBlocks();

		// Bootstrap Angular
		var taskSlayerApp = angular.module('taskSlayerApp', ['ngRoute', 'ngAnimate', 'localstorage', 'couchstorage', 'datastorage', 'schema']);

		taskSlayerApp.factory('dataModelService', ['$rootScope', 'datastorage', function ($rootScope, datastorage) {

			var service = {

				keyBase: undefined,

				model: {},

				SaveState: function () {
					datastorage.save(service.model, service.RestoreState);
				},

				RestoreState: function (event, args) {
					if (typeof keyBase === 'undefined')
						keyBase = "TaskSlayer-" + args.location;

					datastorage.load(keyBase, function(data) {
						service.model = data;

						$rootScope.$broadcast("staterestored", args);
					});
				}
			}

			$rootScope.$on("savestate", service.SaveState);
			$rootScope.$on("restorestate", service.RestoreState);

			return service;
		}]);

		taskSlayerApp.config(['$routeProvider', function($routeProvider) {
			$routeProvider.when('', { controller: 'taskSlayerCtrl'})
				.otherwise({ controller: 'taskSlayerCtrl'});
		}]);

		taskSlayerApp.controller('taskSlayerCtrl', ['$scope', '$rootScope', '$route', '$timeout', '$location', 'dataModelService', function($scope, $rootScope, $route, $timeout, $location, dataModelService) {

			$scope.message = "";
			$scope.showMessage = false;

			$scope.dataModelService = dataModelService;
			$scope.categories = dataModelService.model.taskCategories;

			$scope.change = function() {
				$rootScope.$broadcast('savestate');
			};

			$scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) {
				var loc = $location.absUrl().replace(/[/:]/g, "-");
				$rootScope.$broadcast('restorestate', {location: loc});
			});

			$scope.$on("staterestored", function(event, args) {
				args = (typeof args === "undefined") ? {} : args;

				// Update shortcut after state has been restored asynchronously
				$scope.categories = dataModelService.model.taskCategories;

				console.log("State restored event");
				console.log(args);
				if (args.message) {
					// Let the display stabilise before displaying the message
					$timeout(function() {
						$scope.message = args.message;
						$scope.showMessage = true;

						$timeout(function() {
							$scope.showMessage = false;
						}, 2500);
					}, 1000);
				}
			});

			$scope.addTask = function(category, task, persist) {
				task = (typeof task === "undefined") ? {description: "", done: false} : task;
				persist = (typeof persist === "undefined") ? true : persist;

				category.tasks.list.push(task);

				if (persist)
					$rootScope.$broadcast('savestate');
			};

			$scope.addTaskAfterIndex = function(category, index) {
				category.tasks.list.splice(index + 1, 0, {description: "", done: false});
				$rootScope.$broadcast('savestate');
			};

			$scope.deleteTask = function(category, index, persist) {
				persist = (typeof persist === "undefined") ? true : persist;

				task = category.tasks.list.splice(index, 1)[0];

				var target;

				if (index == category.tasks.list.length && index > 0) {
					target = index - 1;
				} else {
					target = index;
				}

				$scope.focusTask(category, target);

				if (persist)
					$rootScope.$broadcast('savestate');

				return task;
			};

			$scope.focusTask = function(category, index) {
				if (category.tasks.list.length > index && index >= 0) {
					setTimeout(function() {
						$("#" + category.description + "-" + index).focus();
					}, 0);
				}
			};

			$scope.moveTask = function(category, index, direction) {
				var target = index + direction;

				if (target < 0 || target >= category.tasks.list.length) {
					return;
				}

				var tmp = category.tasks.list[index];
				category.tasks.list[index] = category.tasks.list[target];
				category.tasks.list[target] = tmp;

				$rootScope.$broadcast('savestate');

				$scope.focusTask(category, target);
			};

			$scope.moveTaskToCategory = function(destination, source, index) {
				if (destination === source)
					return;

				task = $scope.deleteTask(source, index, false);

				$scope.addTask(destination, task);
			};

			$scope.focusCategory = function(category) {
				if (category.tasks.list.length == 0)
					$scope.addTask(category);
				else
					$scope.focusTask(category, 0);
			}

			$scope.keypress = function($event) {
				var isHandledHere = true;

				if ($event.altKey && !$event.shiftKey) {
					switch($event.keyCode) {
					case 78: // N
						$scope.focusCategory($scope.categories.urgentImportant);
						break;
					case 83: // S
						$scope.focusCategory($scope.categories.important);
						break;
					case 68: // D
						$scope.focusCategory($scope.categories.urgent);
						break;
					case 87: // W
						$scope.focusCategory($scope.categories.waste);
						break;
					default:
						isHandledHere = false;
					}
				} else
					isHandledHere = false;

				if (isHandledHere)
					$event.preventDefault();
			};

			$scope.taskKeypress = function($event, category, index) {
				var isHandledHere = true;

				if ($event.altKey && $event.shiftKey) {
					switch($event.keyCode) {
					case 78: // N
						$scope.moveTaskToCategory($scope.categories.urgentImportant, category, index);
						break;
					case 83: // S
						$scope.moveTaskToCategory($scope.categories.important, category, index);
						break;
					case 68: // D
						$scope.moveTaskToCategory($scope.categories.urgent, category, index);
						break;
					case 87: // W
						$scope.moveTaskToCategory($scope.categories.waste, category, index);
						break;
					default:
						isHandledHere = false;
					}
				} else if ($event.ctrlKey) {
					switch($event.keyCode) {
					case 45: // Insert
						$scope.addTask(category);
						break;
					case 8: // Backspace
					case 46: // Delete
					case 13: // Enter/Return
						$scope.deleteTask(category, index);
						break;
					case 38: // Up
						$scope.moveTask(category, index, -1);
						break;
					case 40: // Down
						$scope.moveTask(category, index, 1);
						break;
					default:
						isHandledHere = false;
					}
				} else if (!$event.ctrlKey && !$event.altKey) {
					switch($event.keyCode) {
					case 13: // Enter/Return
						$scope.addTaskAfterIndex(category, index);
						break;
					case 38: // Up
						$scope.focusTask(category, index - 1);
						break;
					case 40: // Down
						$scope.focusTask(category, index + 1);
						break;
					default:
						isHandledHere = false;
					}
				}

				if (isHandledHere)
					$event.preventDefault();
			};
		}]);

		taskSlayerApp.directive('taskCategory', function() {
			return {
				restrict: 'E',
				scope: {
					category: '=',
					deleteTask: '&',
					addTask: '&',
					change: '&',
					taskKeypress: '&',
					title: '@'
                },
				templateUrl: 'angular-templates/task-category.html'
			};
		});

		taskSlayerApp.directive('initialFocus', function() {
			var timer;

			return function(scope, element, attr) {
				if (timer) clearTimeout(timer);

				timer = setTimeout(function() {
					element[0].focus();
				}, 0);
			}
		});

		angular.bootstrap(document, ['taskSlayerApp']);

		// Hide the loading pane...
		$("#wait-pane-master").css({visibility: "hidden"});

//	});

});

<|endoftext|>