<commit_before>/**
 * Create a link between the 2 given objects.
 * @param obj1
 * @param obj2
 * @param step Value of the curve anchor points.
 * @returns {Raphael.fn}
 */
Raphael.fn.connection = function (obj1, obj2, step) {
    step = step || 40;
    var generatePath = function (obj1, obj2) {
        var box1 = obj1.getBBox(),
            box2 = obj2.getBBox(),
            x1 = box1.x + box1.width / 2,
            y1 = box1.y + box1.height / 2,
            x4 = box2.x + box2.width / 2,
            y4 = box2.y + box2.height / 2;
        return ["M", x1, y1, "C", x1 + step, y1, x4 - step, y4, x4, y4].join(",");
    };
    var obj = this.path(generatePath(obj1, obj2));
    obj.obj1 = obj1;
    obj.obj2 = obj2;
    obj1.attr({"fill": TYPES[obj1.valueType], "stroke": "none"});
    obj2.attr({"fill": TYPES[obj2.valueType], "stroke": "none"});
    obj.update = function () {
        obj.attr({path: generatePath(obj1, obj2)});
    };
    obj.destroy = function () {
        // remove the color only if there is no other connections on this point
        if (obj1.action.countConnection(obj1) < 1) {
            obj1.attr({"fill": "#222", "stroke": TYPES[obj1.valueType]});
        }
        obj2.attr({"fill": "#222", "stroke": TYPES[obj2.valueType]});
        obj.remove();
    };
    return obj;
};<commit_msg>Add click callback on line<commit_after>/**
 * Create a link between the 2 given objects.
 * @param obj1
 * @param obj2
 * @param step Value of the curve anchor points.
 * @returns {Raphael.fn}
 */
Raphael.fn.connection = function (obj1, obj2, step) {
    step = step || 40;
    var generatePath = function (obj1, obj2) {
        var box1 = obj1.getBBox(),
            box2 = obj2.getBBox(),
            x1 = box1.x + box1.width / 2,
            y1 = box1.y + box1.height / 2,
            x4 = box2.x + box2.width / 2,
            y4 = box2.y + box2.height / 2;
        return ["M", x1, y1, "C", x1 + step, y1, x4 - step, y4, x4, y4].join(",");
    };
    var obj = this.path(generatePath(obj1, obj2));
    obj.obj1 = obj1;
    obj.obj2 = obj2;
    obj1.attr({"fill": TYPES[obj1.valueType], "stroke": "none"});
    obj2.attr({"fill": TYPES[obj2.valueType], "stroke": "none"});
    obj.click(function () {
        console.log("line clicked!");
    });
    obj.update = function () {
        obj.attr({path: generatePath(obj1, obj2)});
    };
    obj.destroy = function () {
        // remove the color only if there is no other connections on this point
        if (obj1.action.countConnection(obj1) < 1) {
            obj1.attr({"fill": "#222", "stroke": TYPES[obj1.valueType]});
        }
        obj2.attr({"fill": "#222", "stroke": TYPES[obj2.valueType]});
        obj.remove();
    };
    return obj;
};<|endoftext|>


<commit_before>$("#view_drinks").click(function() {
    var selected_drinks = [];
    var checkedBoxes = $('input[name=ingredient]:checked')
	.each(function(i, selected){
	    selected_drinks[i] = '"' + $(selected).val() + '"';
	});
    get_drinks(selected_drinks);
});

function get_drinks(drinks_list){
    console.log(drinks_list);
    var data_to_send =  "{\"ingredients\": [" + drinks_list + "]}";
    console.log(data_to_send);
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "/subset",
        dataType: "json",
        async: true,
        data: data_to_send,
        success: function (data) {
            console.log("success!");
	    var subset_drinks_list = data["drinks"]["result"];
	    var limes_drinks_list = data["extended_drinks"]["result"];
	    console.log(subset_drinks_list);
	    console.log(limes_drinks_list);
	    $(".panel-default").remove();
	    $("#search_results").show();

	    document.getElementById('subset_drinks_container')
		.appendChild(make_drinks_accordion(subset_drinks_list, "subset_accordion"));
	    document.getElementById('limes_drinks_container')
		.appendChild(make_drinks_accordion(limes_drinks_list, "n_more_accordion"));

	    $(".drink_item").click(function() {
		console.log('here');
		console.log(this);
		that = this;
	    });

        },
        error: function (result) {
        }
    })
}

function makeUL(array) {
    // http://stackoverflow.com/questions/11128700/
    // create-a-ul-and-fill-it-based-on-a-passed-array

    var list = document.createElement('ul');
    for(var i = 0; i < array.length; i++) {
        var item = document.createElement('li');
        item.appendChild(document.createTextNode(array[i]["name"]));
        list.appendChild(item);
    }
    return list;
}

function add_glass_img(glass_str, div_to_add_to) {

    // Cocktail glass by Marieva Cunha from the Noun Project
    // Highball glass by Adam James from the Noun Project
    // Double rocks glass by Gabriela Muñiz from the Noun Project
    // Tall glass by Anton Scherbik from the Noun Project
    // Rocks glass/tumbler by factor[e] design initiative from the Noun Project
    var img=document.createElement("img");
    img.src="static/img/" + glass_str + ".svg";
    img.height="20";
    img.style.marginRight = "20px";
    div_to_add_to.appendChild(img);

}

function make_drinks_accordion(array, accordion_id) {

    var accordion = document.getElementById(accordion_id);
    for(var i = 0; i < array.length; i++) {
	var drink_name_nice = array[i]["name"];
	var drink_name = drink_name_nice.replace(/\W+/g, "");
	var glass_str = array[i]["glass_type"].replace(/ /g, "_");

	var item = document.createElement("div");
	item.className = "panel panel-default";

	var bs_panelheading = document.createElement("div");
	bs_panelheading.className = "panel-heading";
	bs_panelheading.setAttribute("role", "tab");
	bs_panelheading.id = "heading" + drink_name;
	item.appendChild(bs_panelheading);
	if (array[i].hasOwnProperty("recognitions")) {
	    bs_panelheading.style.color = "red";
	}

	var bs_paneltitle = document.createElement("h4");
	bs_paneltitle.className = "panel-title";
	bs_panelheading.appendChild(bs_paneltitle);

	add_glass_img(glass_str, bs_paneltitle);

	panel_button = document.createElement("a");
	panel_button.setAttribute("role","button");
	panel_button.setAttribute("data-toggle", "collapse");
	panel_button.setAttribute("data-parent", "#accordion");

	$(panel_button).attr("aria-expanded","false");
	$(panel_button).attr("aria-controls","collapse" + drink_name);
	panel_button.href = "#collapse" + drink_name;
	panel_button.text = drink_name_nice;
	bs_paneltitle.appendChild(panel_button);

	var collapsed_section = document.createElement("div");
	collapsed_section.id = "collapse" + drink_name;
	collapsed_section.className = "panel-collapse collapse";
	collapsed_section.role = "tabpanel";
	collapsed_section.setAttribute("aria-labelledby", "heading" + drink_name);

	var collapsed_body = document.createElement("div");
	collapsed_body.className = "panel-body";
	collapsed_body.innerHTML = array[i]["instructions"];

	collapsed_section.appendChild(collapsed_body);

	item.appendChild(collapsed_section);
	accordion.appendChild(item);
    }
    return accordion;
}

<commit_msg>added the missing ingredient<commit_after>$("#view_drinks").click(function() {
    var selected_drinks = [];
    var checkedBoxes = $('input[name=ingredient]:checked')
	.each(function(i, selected){
	    selected_drinks[i] = '"' + $(selected).val() + '"';
	});
    get_drinks(selected_drinks);
});

function get_drinks(drinks_list){
    console.log(drinks_list);
    var data_to_send =  "{\"ingredients\": [" + drinks_list + "]}";
    console.log(data_to_send);
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "/subset",
        dataType: "json",
        async: true,
        data: data_to_send,
        success: function (data) {
            console.log("success!");
	    var subset_drinks_list = data["drinks"]["result"];
	    var limes_drinks_list = data["extended_drinks"]["result"];
	    console.log(subset_drinks_list);
	    console.log(limes_drinks_list);
	    $(".panel-default").remove();
	    $("#search_results").show();

	    document.getElementById('subset_drinks_container')
		.appendChild(make_drinks_accordion(subset_drinks_list, "subset_accordion"));
	    document.getElementById('limes_drinks_container')
		.appendChild(make_drinks_accordion(limes_drinks_list, "n_more_accordion"));

	    $(".drink_item").click(function() {
		console.log('here');
		console.log(this);
		that = this;
	    });

        },
        error: function (result) {
        }
    })
}

function makeUL(array) {
    // http://stackoverflow.com/questions/11128700/
    // create-a-ul-and-fill-it-based-on-a-passed-array

    var list = document.createElement('ul');
    for(var i = 0; i < array.length; i++) {
        var item = document.createElement('li');
        item.appendChild(document.createTextNode(array[i]["name"]));
        list.appendChild(item);
    }
    return list;
}

function add_glass_img(glass_str, div_to_add_to) {

    // Cocktail glass by Marieva Cunha from the Noun Project
    // Highball glass by Adam James from the Noun Project
    // Double rocks glass by Gabriela Muñiz from the Noun Project
    // Tall glass by Anton Scherbik from the Noun Project
    // Rocks glass/tumbler by factor[e] design initiative from the Noun Project
    var img=document.createElement("img");
    img.src="static/img/" + glass_str + ".svg";
    img.height="20";
    img.style.marginRight = "20px";
    div_to_add_to.appendChild(img);

}

function make_drinks_accordion(array, accordion_id) {

    var accordion = document.getElementById(accordion_id);
    for(var i = 0; i < array.length; i++) {
	var drink_name_nice = array[i]["name"];
	var drink_name = drink_name_nice.replace(/\W+/g, "");
	var glass_str = array[i]["glass_type"].replace(/ /g, "_");

	var item = document.createElement("div");
	item.className = "panel panel-default";

	var bs_panelheading = document.createElement("div");
	bs_panelheading.className = "panel-heading";
	bs_panelheading.setAttribute("role", "tab");
	bs_panelheading.id = "heading" + drink_name;
	item.appendChild(bs_panelheading);
	if (array[i].hasOwnProperty("recognitions")) {
	    bs_panelheading.style.color = "red";
	}

	var bs_paneltitle = document.createElement("h4");
	bs_paneltitle.className = "panel-title";
	bs_panelheading.appendChild(bs_paneltitle);

	add_glass_img(glass_str, bs_paneltitle);

	panel_button = document.createElement("a");
	panel_button.setAttribute("role","button");
	panel_button.setAttribute("data-toggle", "collapse");
	panel_button.setAttribute("data-parent", "#accordion");

	$(panel_button).attr("aria-expanded","false");
	$(panel_button).attr("aria-controls","collapse" + drink_name);
	panel_button.href = "#collapse" + drink_name;
	panel_button.text = drink_name_nice;
	bs_paneltitle.appendChild(panel_button);

	var collapsed_section = document.createElement("div");
	collapsed_section.id = "collapse" + drink_name;
	collapsed_section.className = "panel-collapse collapse";
	collapsed_section.role = "tabpanel";
	collapsed_section.setAttribute("aria-labelledby", "heading" + drink_name);

	var collapsed_body = document.createElement("div");
	collapsed_body.className = "panel-body";
	collapsed_body.innerHTML = array[i]["instructions"];

	if (accordion_id == "n_more_accordion") {
	    add_missing_ingredient(array[i], collapsed_body);
	}

	collapsed_section.appendChild(collapsed_body);

	item.appendChild(collapsed_section);
	accordion.appendChild(item);
    }
    return accordion;
}

function add_missing_ingredient(drink_obj, parent) {
    var missing = document.createElement("div");
    var text = drink_obj.ingredients[0].ingredient
    missing.innerHTML = "<i> missing " + text + "</i>";
    parent.appendChild(missing);
}

<|endoftext|>


<commit_before>test('multiple elements with arguments works', function (assert) {
  var $ = require('jquery');
  require('jquery.select2');

  var $first = $(
    '<select>' +
      '<option>1</option>' +
      '<option>2</option>' +
    '</select>'
  );
  var $second = $first.clone();

  var $both = $first.add($second);
  $both.select2();

  $both.select2('val', '2');

  assert.equal(
    $first.val(),
    '2',
    'The call should change the value on the first element'
  );
  assert.equal(
    $second.val(),
    '2',
    'The call should also change the value on the second element'
  );
});

test('initializes correctly when jQuery $.data contains cyclic reference object', function (assert) {
  var $ = require('jquery');
  require('jquery.select2');

  var $select = $(
    '<select>' +
      '<option>One</option>' +
      '<option>Two</option>' +
      '<option value="3" selected>Three</option>' +
    '</select>'
  );

  // Add a circular reference object using jQuery.
  var recursiveObject = {};

  recursiveObject['same'] =  recursiveObject;

  $select.data('same', recursiveObject);

  $select.select2();

  assert.equal(
    $select.val(),
    '3',
    'The option value should be pulled correctly'
  );
});<commit_msg>Fixed jshint warnings<commit_after>test('multiple elements with arguments works', function (assert) {
  var $ = require('jquery');
  require('jquery.select2');

  var $first = $(
    '<select>' +
      '<option>1</option>' +
      '<option>2</option>' +
    '</select>'
  );
  var $second = $first.clone();

  var $both = $first.add($second);
  $both.select2();

  $both.select2('val', '2');

  assert.equal(
    $first.val(),
    '2',
    'The call should change the value on the first element'
  );
  assert.equal(
    $second.val(),
    '2',
    'The call should also change the value on the second element'
  );
});

test('initializes correctly when jQuery $.data contains cyclic reference object', function (assert) {
  var $ = require('jquery');
  require('jquery.select2');

  var $select = $(
    '<select>' +
      '<option>One</option>' +
      '<option>Two</option>' +
      '<option value="3" selected>Three</option>' +
    '</select>'
  );

  // Add a circular reference object using jQuery.
  var recursiveObject = {};

  recursiveObject.same =  recursiveObject;

  $select.data('same', recursiveObject);

  $select.select2();

  assert.equal(
    $select.val(),
    '3',
    'The option value should be pulled correctly'
  );
});<|endoftext|>


<commit_before>/**
 * lawg.js
 *
 * A logging and debugging helper for the browser
 * Author: Marc Diethelm
 *
 * see README-lawg.md
 */

(function() {

	var  w = window
		,c
		,isElement
		,isjQuery
		,isArray
	;

	function concatArgs(args, nestedCall) {
		var aStr = []
			,i
			,arg
			,delim = nestedCall ? ', ' : ',\n'
		;

		for (i = 0; i<args.length; i++) {
			arg = args[i];
			if (arg !== undefined && arg !== null) {

				if (isArray(arg))
					aStr.push('[' + arguments.callee(Array.prototype.slice.call(arg), true) + ']');
				// if this is an DOM element node, show an appropriate representation.
				else if (isElement(arg))
					aStr.push( elementToString(arg) );
				// if this is a jQuery object, use the log method plugin defined below
				else if (isjQuery(arg))
					aStr.push(jQueryToString(arg));
				// if it's a NodeList 'convert' it to an array and then... magic
				else if (arg.toString() == '[object NodeList]')
					aStr.push('NodeList[' + arguments.callee(Array.prototype.slice.call(arg), true) + ']');
				// for everything else use native JS toString methods. Or fail miserably! :((
				else
					aStr.push(arg.toString());
			 // arg is either undefined or null. Let the JS engine convert it to string.
			} else
				aStr.push(arg + '');

		}
		return aStr.join(delim);
	}

	function elementToString(elem) {
		var htmlId = ''
		    ,htmlClass = ''
		;
		elem.id && (htmlId = '#' + elem.id);
		elem.className && (htmlClass = '.' + elem.className.replace(/\s/g, '.')); // replace any white space with a '.'
		return elem.tagName + htmlId + htmlClass;
	}

	function jQueryToString($obj) {
		var i
			,stringArray = []
		;
		for (i=0; i<$obj.length; i++) {
			stringArray.push( elementToString($obj[i]) );
		}

		return '$('+ stringArray.join(', ') +')';
	}

	//Resulting function returns true if param is a DOM element
	if (typeof HTMLElement == 'function') {
		isElement = function(obj) { return obj instanceof HTMLElement }
	}
	else {
		isElement = function(obj) { return typeof obj == 'object' && obj.nodeType === 1 && typeof obj.nodeName==='string' }
	}

	// Resulting function returns true if param is a jQuery object
	try {
		$() instanceof jQuery; // abusing try/catch for flow control. Old crappy browsers (I'm lookin at you IE) will throw.
		isjQuery = function(obj) { return obj instanceof jQuery }
	} catch (e) {
		isjQuery = function(obj) { return typeof obj == 'object' && 'jquery' in obj && typeof obj.jquery == 'string' }
	}

	isArray = Array.isArray || function(obj) {
		return toString.call(obj) == '[object Array]';
	};

	if (w.console) {
		c = console;
		if ('dir' in c && 'apply' in c.dir) { // create global shortcuts
			typeof c.log   === 'function' && ( w.log   = function() { c.log.apply(c, arguments) } );
			typeof c.info  === 'function' && ( w.info  = function() { c.info.apply(c, arguments) } );
			typeof c.debug === 'function' && ( w.debug = function() { c.debug.apply(c, arguments) } );
			typeof c.error === 'function' && ( w.error = function() { c.error.apply(c, arguments) } );
			typeof c.dir   === 'function' && ( w.dir   = function() { c.dir.apply(c, arguments) } );
			typeof c.table === 'function' && ( w.table = function() { c.table.apply(c, arguments) } );
		}
		else { // IE: we have console.log but it just accepts one param. let's fix that! :)
			w.log = function() {
				c.log(concatArgs(arguments));
			}
		}

		clear = c.clear; // supported in IE and Fx
	}
	else {
		w.log = function() { alert(concatArgs(arguments)) };
	}

	w._alert = function() { alert(concatArgs(arguments)) };

	/**
	 * A tiny jQuery plugin adding logging to any jQuery object
	 * @param clear Boolean Clear the console before logging?
	 */

	w.jQuery && (jQuery.fn.log = function(clear) {
		clear && c.clear.call(c);
		c.log.call(c, this);
		return this;
	});

	w.jQuery && (jQuery.fn.alert = function() {
		w._alert(concatArgs(this));
	});

})();
<commit_msg>update lawg.js<commit_after>/**
 * Global development and debugging helper
 * Author: Marc Diethelm
 */

;(function() {

	var  w = window
		,c
		,$ = w.jQuery || w.Zepto || null
		,isElement
		,isjQuery
		,isArray
		,fn = 'function'
		,cMethods = ['log', 'info', 'debug', 'warn', 'error', 'dir', 'table']
		,method
		,i
		,len
	;

	function concatArgs(args, nestedCall) {
		var aStr = []
			,i
			,arg
			,delim = nestedCall ? ', ' : ',\n'
		;

		for (i = 0; i<args.length; i++) {
			arg = args[i];
			if (arg !== undefined && arg !== null) {

				if (isArray(arg))
					aStr.push('[' + arguments.callee(Array.prototype.slice.call(arg), true) + ']');
				// if this is an DOM element node, show an appropriate representation.
				else if (isElement(arg))
					aStr.push( elementToString(arg) );
				// if this is a jQuery object, use the log method plugin defined below
				else if (isjQuery(arg))
					aStr.push(jQueryToString(arg));
				// if it's a NodeList 'convert' it to an array and then... magic
				else if (arg.toString() == '[object NodeList]')
					aStr.push('NodeList[' + arguments.callee(Array.prototype.slice.call(arg), true) + ']');
				// for everything else use native JS toString methods. Or fail miserably! :((
				else
					aStr.push(arg.toString());
			 // arg is either undefined or null. Let the JS engine convert it to string.
			} else
				aStr.push(arg + '');

		}
		return aStr.join(delim);
	}

	function elementToString(elem) {
		var htmlId = ''
		    ,htmlClass = ''
		;
		elem.id && (htmlId = '#' + elem.id);
		elem.className && (htmlClass = '.' + elem.className.replace(/\s/g, '.')); // replace any white space with a '.'
		return elem.tagName + htmlId + htmlClass;
	}

	function jQueryToString($obj) {
		var i
			,stringArray = []
		;
		for (i=0; i<$obj.length; i++) {
			stringArray.push( elementToString($obj[i]) );
		}

		return '$('+ stringArray.join(', ') +')';
	}

	//Resulting function returns true if param is a DOM element
	if (typeof HTMLElement == 'function') {
		isElement = function(obj) { return obj instanceof HTMLElement }
	}
	else {
		isElement = function(obj) { return typeof obj == 'object' && obj.nodeType === 1 && typeof obj.nodeName==='string' }
	}

	// Resulting function returns true if param is a jQuery object
	try {
		$() instanceof $; // abusing try/catch for flow control. Old crappy browsers (I'm lookin at you IE) will throw.
		isjQuery = function(obj) { return obj instanceof $ }
	} catch (e) {
		isjQuery = function(obj) { return typeof obj == 'object' && 'jquery' in obj && typeof obj.jquery == 'string' }
	}

	isArray = Array.isArray || function(obj) {
		return Object.prototype.toString.call(obj) == '[object Array]';
	};

	if (w.console) {
		c = console;
		if ('dir' in c && 'apply' in c.dir) { // create global shortcuts
			for (i=0, len = cMethods.length; i<len && (method = cMethods[i]); i++) {
				typeof c[method] === fn && ( function(method) { // create a new scope to preserve the value of method
					w[method] = function() { c[method].apply(c, arguments) }
				} )(method);
			}
		}
		else { // IE: we have console.log but it just accepts one param. let's fix that! :)
			w.log = function() {
				c.log(concatArgs(arguments));
			}
		}

		clear = c.clear; // supported in IE and Fx
	}
	else {
		w.log = function() { alert(concatArgs(arguments)) };
	}

	w._alert = function() { alert(concatArgs(arguments)) };

	/**
	 * A tiny jQuery plugin adding logging to any jQuery object
	 * @param clear Boolean Clear the console before logging?
	 */

	$ && ($.fn.log = function(clear) {
		clear && c.clear.call(c);
		c.log.call(c, this);
		return this;
	});

	$ && ($.fn.alert = function() {
		w._alert(concatArgs(this));
	});

})();
<|endoftext|>


<commit_before>'use strict';

// Function to assign defaults for missing values
// Overwrite arrays, merge objects, always favor obj
const setDefaults = (obj, defaults) => {
  for(var key in obj) {
    // If item is an object, create an array or object if it doesn't exist in the defaults
    if(typeofObject(obj[key]) && !typeofObject(defaults[key]))
      defaults[key] = isArray(obj[key]) ? [] : {};

    defaults[key] = (isObject(obj[key]) && !isArray(defaults[key])) ?
      // Recursively run for child for objects
      setDefaults(obj[key], defaults[key]) :
      // Overwrite arrays, and all other data types
      obj[key];
  }
  return defaults;
};

// Returns true for objects and arrays
const typeofObject = item => item !== null && typeof item === 'object';

// Returns true for arrays, but not objects
const isArray = item => Array.isArray(item);

// Returns true for objects, but not arrays
const isObject = item => classIs('Object', item);

// Returns true if node is running on macOS
const isMacOS = () => 'darwin' === process.platform;

// Returns true if defined and not null
const notEmpty = item => item !== undefined && item !== null;

// Returns the class - https://bonsaiden.github.io/JavaScript-Garden/#types
const classType = item => Object.prototype.toString.call(item).slice(8, -1);

// Check the class of an item
const classIs = (type, item) => notEmpty(item) && classType(item) === type;

module.exports = {
  setDefaults,
  isMacOS,
  typeofObject,
  isArray,
  isObject,
  classType,
  classIs,
};<commit_msg>Arrange code<commit_after>'use strict';

// -- Data type functions

// Returns true for objects and arrays
const typeofObject = item => item !== null && typeof item === 'object';

// Returns the class - https://bonsaiden.github.io/JavaScript-Garden/#types
const classType = item => Object.prototype.toString.call(item).slice(8, -1);

// Check the class of an item
const classIs = (type, item) => notEmpty(item) && classType(item) === type;

// Returns true for arrays, but not objects
const isArray = item => Array.isArray(item);

// Returns true for objects, but not arrays
const isObject = item => classIs('Object', item);

// Returns true if defined and not null
const notEmpty = item => item !== undefined && item !== null;

// -- System functions

// Returns true if node is running on macOS
const isMacOS = () => 'darwin' === process.platform;

// -- Object functions

// Function to assign defaults for missing values
// Overwrite arrays, merge objects, always favor obj
const setDefaults = (obj, defaults) => {
  for(var key in obj) {
    // If item is an object, create an array or object if it doesn't exist in the defaults
    if(typeofObject(obj[key]) && !typeofObject(defaults[key]))
      defaults[key] = isArray(obj[key]) ? [] : {};

    defaults[key] = (isObject(obj[key]) && !isArray(defaults[key])) ?
      // Recursively run for child for objects
      setDefaults(obj[key], defaults[key]) :
      // Overwrite arrays, and all other data types
      obj[key];
  }
  return defaults;
};

// -- Export

module.exports = {
  setDefaults,
  isMacOS,
  typeofObject,
  isArray,
  isObject,
  classType,
  classIs,
};<|endoftext|>


<commit_before>function date (format, timestamp) {
  // http://kevin.vanzonneveld.net
  // +   original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
  // +      parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html)
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: MeEtc (http://yass.meetcweb.com)
  // +   improved by: Brad Touesnard
  // +   improved by: Tim Wiel
  // +   improved by: Bryan Elliott
  // +   improved by: David Randall
  // +      input by: Brett Zamir (http://brett-zamir.me)
  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: Theriault
  // +  derived from: gettimeofday
  // +      input by: majak
  // +   bugfixed by: majak
  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +      input by: Alex
  // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
  // +   improved by: Theriault
  // +   improved by: Brett Zamir (http://brett-zamir.me)
  // +   improved by: Theriault
  // +   improved by: Thomas Beaucourt (http://www.webapp.fr)
  // +   improved by: JT
  // +   improved by: Theriault
  // +   improved by: Rafał Kukawski (http://blog.kukawski.pl)
  // +   bugfixed by: omid (http://phpjs.org/functions/380:380#comment_137122)
  // +      input by: Martin
  // +      input by: Alex Wilson
  // +      input by: Haravikk
  // +   improved by: Theriault
  // +   bugfixed by: Chris (http://www.devotis.nl/)
  // %        note 1: Uses global: php_js to store the default timezone
  // %        note 2: Although the function potentially allows timezone info (see notes), it currently does not set
  // %        note 2: per a timezone specified by date_default_timezone_set(). Implementers might use
  // %        note 2: this.php_js.currentTimezoneOffset and this.php_js.currentTimezoneDST set by that function
  // %        note 2: in order to adjust the dates in this function (or our other date functions!) accordingly
  // *     example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400);
  // *     returns 1: '09:09:40 m is month'
  // *     example 2: date('F j, Y, g:i a', 1062462400);
  // *     returns 2: 'September 2, 2003, 2:26 am'
  // *     example 3: date('Y W o', 1062462400);
  // *     returns 3: '2003 36 2003'
  // *     example 4: x = date('Y m d', (new Date()).getTime()/1000);
  // *     example 4: (x+'').length == 10 // 2009 01 09
  // *     returns 4: true
  // *     example 5: date('W', 1104534000);
  // *     returns 5: '53'
  // *     example 6: date('B t', 1104534000);
  // *     returns 6: '999 31'
  // *     example 7: date('W U', 1293750000.82); // 2010-12-31
  // *     returns 7: '52 1293750000'
  // *     example 8: date('W', 1293836400); // 2011-01-01
  // *     returns 8: '52'
  // *     example 9: date('W Y-m-d', 1293974054); // 2011-01-02
  // *     returns 9: '52 2011-01-02'
    var that = this,
      jsdate,
      f,
      // Keep this here (works, but for code commented-out
      // below for file size reasons)
      //, tal= [],
      txt_words = ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
      // trailing backslash -> (dropped)
      // a backslash followed by any character (including backslash) -> the character
      // empty string -> empty string
      formatChr = /\\?(.?)/gi,
      formatChrCb = function (t, s) {
        return f[t] ? f[t]() : s;
      },
      _pad = function (n, c) {
        n = n.toString();
        return n.length < c ? _pad('0' + n, c) : n;
      };
  f = {
    // Day
    d: function () { // Day of month w/leading 0; 01..31
      return _pad(f.j(), 2);
    },
    D: function () { // Shorthand day name; Mon...Sun
      return f.l().slice(0, 3);
    },
    j: function () { // Day of month; 1..31
      return jsdate.getDate();
    },
    l: function () { // Full day name; Monday...Sunday
      return txt_words[f.w()] + 'day';
    },
    N: function () { // ISO-8601 day of week; 1[Mon]..7[Sun]
      return f.w() || 7;
    },
    S: function(){ // Ordinal suffix for day of month; st, nd, rd, th
      var j = f.j(),
        i = j%10;
      if (i <= 3 && parseInt((j%100)/10, 10) == 1) {
        i = 0;
      }
      return ['st', 'nd', 'rd'][i - 1] || 'th';
    },
    w: function () { // Day of week; 0[Sun]..6[Sat]
      return jsdate.getDay();
    },
    z: function () { // Day of year; 0..365
      var a = new Date(f.Y(), f.n() - 1, f.j()),
        b = new Date(f.Y(), 0, 1);
      return Math.round((a - b) / 864e5);
    },

    // Week
    W: function () { // ISO-8601 week number
      var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3),
        b = new Date(a.getFullYear(), 0, 4);
      return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
    },

    // Month
    F: function () { // Full month name; January...December
      return txt_words[6 + f.n()];
    },
    m: function () { // Month w/leading 0; 01...12
      return _pad(f.n(), 2);
    },
    M: function () { // Shorthand month name; Jan...Dec
      return f.F().slice(0, 3);
    },
    n: function () { // Month; 1...12
      return jsdate.getMonth() + 1;
    },
    t: function () { // Days in month; 28...31
      return (new Date(f.Y(), f.n(), 0)).getDate();
    },

    // Year
    L: function () { // Is leap year?; 0 or 1
      var j = f.Y();
      return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0;
    },
    o: function () { // ISO-8601 year
      var n = f.n(),
        W = f.W(),
        Y = f.Y();
      return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0);
    },
    Y: function () { // Full year; e.g. 1980...2010
      return jsdate.getFullYear();
    },
    y: function () { // Last two digits of year; 00...99
      return f.Y().toString().slice(-2);
    },

    // Time
    a: function () { // am or pm
      return jsdate.getHours() > 11 ? "pm" : "am";
    },
    A: function () { // AM or PM
      return f.a().toUpperCase();
    },
    B: function () { // Swatch Internet time; 000..999
      var H = jsdate.getUTCHours() * 36e2,
        // Hours
        i = jsdate.getUTCMinutes() * 60,
        // Minutes
        s = jsdate.getUTCSeconds(); // Seconds
      return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
    },
    g: function () { // 12-Hours; 1..12
      return f.G() % 12 || 12;
    },
    G: function () { // 24-Hours; 0..23
      return jsdate.getHours();
    },
    h: function () { // 12-Hours w/leading 0; 01..12
      return _pad(f.g(), 2);
    },
    H: function () { // 24-Hours w/leading 0; 00..23
      return _pad(f.G(), 2);
    },
    i: function () { // Minutes w/leading 0; 00..59
      return _pad(jsdate.getMinutes(), 2);
    },
    s: function () { // Seconds w/leading 0; 00..59
      return _pad(jsdate.getSeconds(), 2);
    },
    u: function () { // Microseconds; 000000-999000
      return _pad(jsdate.getMilliseconds() * 1000, 6);
    },

    // Timezone
    e: function () { // Timezone identifier; e.g. Atlantic/Azores, ...
      // The following works, but requires inclusion of the very large
      // timezone_abbreviations_list() function.
/*              return that.date_default_timezone_get();
*/
      throw 'Not supported (see source code of date() for timezone on how to add support)';
    },
    I: function () { // DST observed?; 0 or 1
      // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
      // If they are not equal, then DST is observed.
      var a = new Date(f.Y(), 0),
        // Jan 1
        c = Date.UTC(f.Y(), 0),
        // Jan 1 UTC
        b = new Date(f.Y(), 6),
        // Jul 1
        d = Date.UTC(f.Y(), 6); // Jul 1 UTC
      return ((a - c) !== (b - d)) ? 1 : 0;
    },
    O: function () { // Difference to GMT in hour format; e.g. +0200
      var tzo = jsdate.getTimezoneOffset(),
        a = Math.abs(tzo);
      return (tzo > 0 ? "-" : "+") + _pad(Math.floor(a / 60) * 100 + a % 60, 4);
    },
    P: function () { // Difference to GMT w/colon; e.g. +02:00
      var O = f.O();
      return (O.substr(0, 3) + ":" + O.substr(3, 2));
    },
    T: function () { // Timezone abbreviation; e.g. EST, MDT, ...
      // The following works, but requires inclusion of the very
      // large timezone_abbreviations_list() function.
/*              var abbr = '', i = 0, os = 0, default = 0;
      if (!tal.length) {
        tal = that.timezone_abbreviations_list();
      }
      if (that.php_js && that.php_js.default_timezone) {
        default = that.php_js.default_timezone;
        for (abbr in tal) {
          for (i=0; i < tal[abbr].length; i++) {
            if (tal[abbr][i].timezone_id === default) {
              return abbr.toUpperCase();
            }
          }
        }
      }
      for (abbr in tal) {
        for (i = 0; i < tal[abbr].length; i++) {
          os = -jsdate.getTimezoneOffset() * 60;
          if (tal[abbr][i].offset === os) {
            return abbr.toUpperCase();
          }
        }
      }
*/
      return 'UTC';
    },
    Z: function () { // Timezone offset in seconds (-43200...50400)
      return -jsdate.getTimezoneOffset() * 60;
    },

    // Full Date/Time
    c: function () { // ISO-8601 date.
      return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb);
    },
    r: function () { // RFC 2822
      return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
    },
    U: function () { // Seconds since UNIX epoch
      return jsdate / 1000 | 0;
    }
  };
  this.date = function (format, timestamp) {
    that = this;
    jsdate = (timestamp === undefined ? new Date() : // Not provided
      (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
      new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
    );
    return format.replace(formatChr, formatChrCb);
  };
  return this.date(format, timestamp);
}
<commit_msg>I experienced problems for iPad2 (native browser) using the former implementation, if n is no object and has no toString()-Method. So I replaced with String()<commit_after>function date (format, timestamp) {
  // http://kevin.vanzonneveld.net
  // +   original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
  // +      parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html)
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: MeEtc (http://yass.meetcweb.com)
  // +   improved by: Brad Touesnard
  // +   improved by: Tim Wiel
  // +   improved by: Bryan Elliott
  // +   improved by: David Randall
  // +      input by: Brett Zamir (http://brett-zamir.me)
  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: Theriault
  // +  derived from: gettimeofday
  // +      input by: majak
  // +   bugfixed by: majak
  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +      input by: Alex
  // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
  // +   improved by: Theriault
  // +   improved by: Brett Zamir (http://brett-zamir.me)
  // +   improved by: Theriault
  // +   improved by: Thomas Beaucourt (http://www.webapp.fr)
  // +   improved by: JT
  // +   improved by: Theriault
  // +   improved by: Rafał Kukawski (http://blog.kukawski.pl)
  // +   bugfixed by: omid (http://phpjs.org/functions/380:380#comment_137122)
  // +      input by: Martin
  // +      input by: Alex Wilson
  // +      input by: Haravikk
  // +   improved by: Theriault
  // +   bugfixed by: Chris (http://www.devotis.nl/)
  // %        note 1: Uses global: php_js to store the default timezone
  // %        note 2: Although the function potentially allows timezone info (see notes), it currently does not set
  // %        note 2: per a timezone specified by date_default_timezone_set(). Implementers might use
  // %        note 2: this.php_js.currentTimezoneOffset and this.php_js.currentTimezoneDST set by that function
  // %        note 2: in order to adjust the dates in this function (or our other date functions!) accordingly
  // *     example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400);
  // *     returns 1: '09:09:40 m is month'
  // *     example 2: date('F j, Y, g:i a', 1062462400);
  // *     returns 2: 'September 2, 2003, 2:26 am'
  // *     example 3: date('Y W o', 1062462400);
  // *     returns 3: '2003 36 2003'
  // *     example 4: x = date('Y m d', (new Date()).getTime()/1000);
  // *     example 4: (x+'').length == 10 // 2009 01 09
  // *     returns 4: true
  // *     example 5: date('W', 1104534000);
  // *     returns 5: '53'
  // *     example 6: date('B t', 1104534000);
  // *     returns 6: '999 31'
  // *     example 7: date('W U', 1293750000.82); // 2010-12-31
  // *     returns 7: '52 1293750000'
  // *     example 8: date('W', 1293836400); // 2011-01-01
  // *     returns 8: '52'
  // *     example 9: date('W Y-m-d', 1293974054); // 2011-01-02
  // *     returns 9: '52 2011-01-02'
    var that = this,
      jsdate,
      f,
      // Keep this here (works, but for code commented-out
      // below for file size reasons)
      //, tal= [],
      txt_words = ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
      // trailing backslash -> (dropped)
      // a backslash followed by any character (including backslash) -> the character
      // empty string -> empty string
      formatChr = /\\?(.?)/gi,
      formatChrCb = function (t, s) {
        return f[t] ? f[t]() : s;
      },
      _pad = function (n, c) {
        n = String(n);
        while (n.length < c) n = '0' + n;
        return n;
      };
  f = {
    // Day
    d: function () { // Day of month w/leading 0; 01..31
      return _pad(f.j(), 2);
    },
    D: function () { // Shorthand day name; Mon...Sun
      return f.l().slice(0, 3);
    },
    j: function () { // Day of month; 1..31
      return jsdate.getDate();
    },
    l: function () { // Full day name; Monday...Sunday
      return txt_words[f.w()] + 'day';
    },
    N: function () { // ISO-8601 day of week; 1[Mon]..7[Sun]
      return f.w() || 7;
    },
    S: function(){ // Ordinal suffix for day of month; st, nd, rd, th
      var j = f.j(),
        i = j%10;
      if (i <= 3 && parseInt((j%100)/10, 10) == 1) {
        i = 0;
      }
      return ['st', 'nd', 'rd'][i - 1] || 'th';
    },
    w: function () { // Day of week; 0[Sun]..6[Sat]
      return jsdate.getDay();
    },
    z: function () { // Day of year; 0..365
      var a = new Date(f.Y(), f.n() - 1, f.j()),
        b = new Date(f.Y(), 0, 1);
      return Math.round((a - b) / 864e5);
    },

    // Week
    W: function () { // ISO-8601 week number
      var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3),
        b = new Date(a.getFullYear(), 0, 4);
      return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
    },

    // Month
    F: function () { // Full month name; January...December
      return txt_words[6 + f.n()];
    },
    m: function () { // Month w/leading 0; 01...12
      return _pad(f.n(), 2);
    },
    M: function () { // Shorthand month name; Jan...Dec
      return f.F().slice(0, 3);
    },
    n: function () { // Month; 1...12
      return jsdate.getMonth() + 1;
    },
    t: function () { // Days in month; 28...31
      return (new Date(f.Y(), f.n(), 0)).getDate();
    },

    // Year
    L: function () { // Is leap year?; 0 or 1
      var j = f.Y();
      return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0;
    },
    o: function () { // ISO-8601 year
      var n = f.n(),
        W = f.W(),
        Y = f.Y();
      return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0);
    },
    Y: function () { // Full year; e.g. 1980...2010
      return jsdate.getFullYear();
    },
    y: function () { // Last two digits of year; 00...99
      return f.Y().toString().slice(-2);
    },

    // Time
    a: function () { // am or pm
      return jsdate.getHours() > 11 ? "pm" : "am";
    },
    A: function () { // AM or PM
      return f.a().toUpperCase();
    },
    B: function () { // Swatch Internet time; 000..999
      var H = jsdate.getUTCHours() * 36e2,
        // Hours
        i = jsdate.getUTCMinutes() * 60,
        // Minutes
        s = jsdate.getUTCSeconds(); // Seconds
      return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
    },
    g: function () { // 12-Hours; 1..12
      return f.G() % 12 || 12;
    },
    G: function () { // 24-Hours; 0..23
      return jsdate.getHours();
    },
    h: function () { // 12-Hours w/leading 0; 01..12
      return _pad(f.g(), 2);
    },
    H: function () { // 24-Hours w/leading 0; 00..23
      return _pad(f.G(), 2);
    },
    i: function () { // Minutes w/leading 0; 00..59
      return _pad(jsdate.getMinutes(), 2);
    },
    s: function () { // Seconds w/leading 0; 00..59
      return _pad(jsdate.getSeconds(), 2);
    },
    u: function () { // Microseconds; 000000-999000
      return _pad(jsdate.getMilliseconds() * 1000, 6);
    },

    // Timezone
    e: function () { // Timezone identifier; e.g. Atlantic/Azores, ...
      // The following works, but requires inclusion of the very large
      // timezone_abbreviations_list() function.
/*              return that.date_default_timezone_get();
*/
      throw 'Not supported (see source code of date() for timezone on how to add support)';
    },
    I: function () { // DST observed?; 0 or 1
      // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
      // If they are not equal, then DST is observed.
      var a = new Date(f.Y(), 0),
        // Jan 1
        c = Date.UTC(f.Y(), 0),
        // Jan 1 UTC
        b = new Date(f.Y(), 6),
        // Jul 1
        d = Date.UTC(f.Y(), 6); // Jul 1 UTC
      return ((a - c) !== (b - d)) ? 1 : 0;
    },
    O: function () { // Difference to GMT in hour format; e.g. +0200
      var tzo = jsdate.getTimezoneOffset(),
        a = Math.abs(tzo);
      return (tzo > 0 ? "-" : "+") + _pad(Math.floor(a / 60) * 100 + a % 60, 4);
    },
    P: function () { // Difference to GMT w/colon; e.g. +02:00
      var O = f.O();
      return (O.substr(0, 3) + ":" + O.substr(3, 2));
    },
    T: function () { // Timezone abbreviation; e.g. EST, MDT, ...
      // The following works, but requires inclusion of the very
      // large timezone_abbreviations_list() function.
/*              var abbr = '', i = 0, os = 0, default = 0;
      if (!tal.length) {
        tal = that.timezone_abbreviations_list();
      }
      if (that.php_js && that.php_js.default_timezone) {
        default = that.php_js.default_timezone;
        for (abbr in tal) {
          for (i=0; i < tal[abbr].length; i++) {
            if (tal[abbr][i].timezone_id === default) {
              return abbr.toUpperCase();
            }
          }
        }
      }
      for (abbr in tal) {
        for (i = 0; i < tal[abbr].length; i++) {
          os = -jsdate.getTimezoneOffset() * 60;
          if (tal[abbr][i].offset === os) {
            return abbr.toUpperCase();
          }
        }
      }
*/
      return 'UTC';
    },
    Z: function () { // Timezone offset in seconds (-43200...50400)
      return -jsdate.getTimezoneOffset() * 60;
    },

    // Full Date/Time
    c: function () { // ISO-8601 date.
      return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb);
    },
    r: function () { // RFC 2822
      return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
    },
    U: function () { // Seconds since UNIX epoch
      return jsdate / 1000 | 0;
    }
  };
  this.date = function (format, timestamp) {
    that = this;
    jsdate = (timestamp === undefined ? new Date() : // Not provided
      (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
      new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
    );
    return format.replace(formatChr, formatChrCb);
  };
  return this.date(format, timestamp);
}
<|endoftext|>


<commit_before>Package.describe({
  summary: "Widgets for autoform and bootstrap 3. Not meant to be used by anyone but me :)"
});

Package.on_use(function(api, where) {
  api.use(['templating', 'handlebars', 'moment-with-langs', 'numeral', 'bower', 'ui'], 'client');
  api.imply(['templating', 'handlebars', 'moment-with-langs', 'numeral', 'bower', 'ui'], 'client');

  api.use(['clockpicker', 'select2'], ['client']);
  api.imply(['clockpicker', 'select2'], ['client']);

  api.add_files([
    'lib/checkboxButton.css',
    'lib/checkboxButton.html',
    'lib/checkboxButton.js'
  ], ['client']);

  api.add_files([
    'lib/date.html',
    'lib/date.js'
  ], ['client']);

  api.add_files([
    'lib/email.html',
    'lib/email.js'
  ], ['client']);

  api.add_files([
    'lib/mobile.html',
    'lib/mobile.js'
  ], ['client']);

  api.add_files([
    'lib/phone.html',
    'lib/phone.js'
  ], ['client']);

  api.add_files([
    'lib/radiosButtons.html',
    'lib/radiosButtons.js'
  ], ['client']);

  api.add_files([
    'lib/tags.html',
    'lib/tags.js'
  ], ['client']);

  api.add_files([
    'lib/time.html',
    'lib/time.js'
  ], ['client']);

  api.add_files([
    'lib/nolabel.html'
  ], ['client']);

  // Install bower components.
  api.add_files('smart.json', 'client');

});

Package.on_test(function(api) {
});
<commit_msg>upgrade for meteor 0.9.0<commit_after>Package.describe({
  summary: "Widgets for autoform and bootstrap 3. Not meant to be used by anyone but me :)"
});

Package.on_use(function(api, where) {
  api.use(['templating', 'spacebars', 'mrt:moment', 'mrt:numeral', 'ui'], 'client');
  api.imply(['templating', 'spacebars', 'mrt:moment', 'mrt:numeral', 'ui'], 'client');

  api.use(['clockpicker', 'select2'], ['client']);
  api.imply(['clockpicker', 'select2'], ['client']);

  api.add_files([
    'lib/checkboxButton.css',
    'lib/checkboxButton.html',
    'lib/checkboxButton.js'
  ], ['client']);

  api.add_files([
    'lib/date.html',
    'lib/date.js'
  ], ['client']);

  api.add_files([
    'lib/email.html',
    'lib/email.js'
  ], ['client']);

  api.add_files([
    'lib/mobile.html',
    'lib/mobile.js'
  ], ['client']);

  api.add_files([
    'lib/phone.html',
    'lib/phone.js'
  ], ['client']);

  api.add_files([
    'lib/radiosButtons.html',
    'lib/radiosButtons.js'
  ], ['client']);

  api.add_files([
    'lib/tags.html',
    'lib/tags.js'
  ], ['client']);

  api.add_files([
    'lib/time.html',
    'lib/time.js'
  ], ['client']);

  api.add_files([
    'lib/nolabel.html'
  ], ['client']);

  // Install bower components.
  api.add_files('smart.json', 'client');

});

Package.on_test(function(api) {
});
<|endoftext|>


<commit_before>const Kraken = require('kraken-api');
const moment = require('moment');
const _ = require('lodash');
const retry = require('../exchangeUtils').retry;

const marketData = require('./kraken-markets.json');

const Trader = function(config) {
  _.bindAll(this);

  if(_.isObject(config)) {
    this.key = config.key;
    this.secret = config.secret;
    this.currency = config.currency.toUpperCase()
    this.asset = config.asset.toUpperCase();
  }

  this.name = 'kraken';
  this.since = null;

  this.market = _.find(Trader.getCapabilities().markets, (market) => {
    return market.pair[0] === this.currency && market.pair[1] === this.asset
  });
  this.pair = this.market.book;

  this.kraken = new Kraken(
    this.key,
    this.secret,
    {timeout: +moment.duration(60, 'seconds')}
  );
}

const recoverableErrors = [
  'SOCKETTIMEDOUT',
  'TIMEDOUT',
  'CONNRESET',
  'CONNREFUSED',
  'NOTFOUND',
  'API:Rate limit exceeded',
  'Service:Unavailable',
  'Request timed out',
  'Empty response',
  'API:Invalid nonce',
  'General:Temporary lockout',
  'Response code 520',
  'Response code 525'
];

// errors that might mean
// the API call succeeded.
const unknownResultErrors = [
  'Response code 502',
]

const includes = (str, list) => {
  if(!_.isString(str))
    return false;

  return _.some(list, item => str.includes(item));
}

Trader.prototype.handleResponse = function(funcName, callback, nonMutating) {
  return (error, body) => {

    if(!error && !body) {
      error = new Error('Empty response');
    }

    if(error) {
      if(includes(error.message, recoverableErrors)) {
        error.notFatal = true;
      }

      if(includes(error.message, ['Rate limit exceeded'])) {
        error.notFatal = true;
        error.backoffDelay = 1000;
      }

      if(!error.notFatal) {
        console.log(funcName, error);
      }

      if(nonMutating && includes(error.message, unknownResultErrors)) {
        // this call only tried to retrieve data, safe to redo
        error.notFatal = true;
      }

      return callback(error);
    }

    return callback(undefined, body);
  }
};

Trader.prototype.getTrades = function(since, callback, descending) {
  const startTs = since ? moment(since).valueOf() : null;

  const handle = (err, trades) => {
    if (err) return callback(err);

    var parsedTrades = [];
    _.each(trades.result[this.pair], function(trade) {
      // Even when you supply 'since' you can still get more trades than you asked for, it needs to be filtered
      if (_.isNull(startTs) || startTs < moment.unix(trade[2]).valueOf()) {
        parsedTrades.push({
          tid: moment.unix(trade[2]).valueOf() * 1000000,
          date: parseInt(Math.round(trade[2]), 10),
          price: parseFloat(trade[0]),
          amount: parseFloat(trade[1])
        });
      }
    }, this);

    if(descending)
      callback(undefined, parsedTrades.reverse());
    else
      callback(undefined, parsedTrades);
  };

  const reqData = {
    pair: this.pair
  };

  if(since) {
    // Kraken wants a tid, which is found to be timestamp_ms * 1000000 in practice. No clear documentation on this though
    reqData.since = startTs * 1000000;
  }

  const fetch = cb => this.kraken.api('Trades', reqData, this.handleResponse('getTrades', cb), true);
  retry(null, fetch, handle);
};

Trader.prototype.getPortfolio = function(callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const assetAmount = parseFloat( data.result[this.market.prefixed[1]] );
    const currencyAmount = parseFloat( data.result[this.market.prefixed[0]] );

    if(!_.isNumber(assetAmount) || _.isNaN(assetAmount)) {
      console.log(`Kraken did not return portfolio for ${this.asset}, assuming 0.`);
      assetAmount = 0;
    }

    if(!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) {
      console.log(`Kraken did not return portfolio for ${this.currency}, assuming 0.`);
      currencyAmount = 0;
    }

    const portfolio = [
      { name: this.asset, amount: assetAmount },
      { name: this.currency, amount: currencyAmount }
    ];

    return callback(undefined, portfolio);
  };

  const fetch = cb => this.kraken.api('Balance', {}, this.handleResponse('getPortfolio', cb), true);
  retry(null, fetch, handle);
};

// This assumes that only limit orders are being placed with standard assets pairs
// It does not take into account volume discounts.
// Base maker fee is 0.16%, taker fee is 0.26%.
Trader.prototype.getFee = function(callback) {
  const makerFee = 0.16;
  callback(undefined, makerFee / 100);
};

Trader.prototype.getTicker = function(callback) {
  const handle = (err, data) => {
    if (err) return callback(err);

    const result = data.result[this.pair];
    const ticker = {
      ask: result.a[0],
      bid: result.b[0]
    };
    callback(undefined, ticker);
  };

  const reqData = {pair: this.pair}
  const fetch = cb => this.kraken.api('Ticker', reqData, this.handleResponse('getTicker', cb), true);
  retry(null, fetch, handle);
};

Trader.prototype.roundAmount = function(amount) {
  return _.floor(amount, this.market.amountPrecision);
};

Trader.prototype.roundPrice = function(amount) {
  return _.round(amount, this.market.pricePrecision);
};

Trader.prototype.addOrder = function(tradeType, amount, price, callback) {
  price = this.roundAmount(price); // only round price, not amount

  console.log(amount, price);

  const handle = (err, data) => {
    if(err) {
      if(includes(err.message, unknownResultErrors)) {
        console.log(new Date, 'ADD ORDER UNKOWN RESULT');
        return this.getRawOpenOrders((err2, orders) => {
          if(err2) {
            return callback(err2);
          }

          _.each(orders, (o, id) => {
            o.id = id;
          });

          const order = _.find(orders, o => {
            if(o.descr.type !== tradeType) {
              return false;
            }

            const ts = moment.unix(o.opentm.split('.')[0])
            if(moment().diff(ts, 'm') > 10) {
              return false;
            }

            // string vs float
            if(o.descr.price != price) {
              console.log(new Date, 'ADD ORDER UNKOWN RESULT, price descrep:', order);
              return false;
            }

            // string vs float
            if(o.vol != amount) {
              console.log(new Date, 'ADD ORDER UNKOWN RESULT, amount descrep:', order);
              return false;
            }

            return true;
          });

          if(!order) {
            return callback(err);
          }

          return callback(undefined, order.id);
        });
      }

      return callback(err);
    }

    const txid = data.result.txid[0];

    callback(undefined, txid);
  };

  const reqData = {
    pair: this.pair,
    type: tradeType.toLowerCase(),
    ordertype: 'limit',
    price: price,
    volume: amount
  };

  const fetch = cb => this.kraken.api('AddOrder', reqData, this.handleResponse('addOrder', cb));
  retry(null, fetch, handle);
};

Trader.prototype.buy = function(amount, price, callback) {
  this.addOrder('buy', amount, price, callback);
};

Trader.prototype.sell = function(amount, price, callback) {
  this.addOrder('sell', amount, price, callback);
};

Trader.prototype.getOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    console.log('getOrder:', order);

    const price = parseFloat( data.result[ order ].price );
    const amount = parseFloat( data.result[ order ].vol_exec );
    const date = moment.unix( data.result[ order ].closetm );

    // TODO: figure out fees, kraken is reporting 0 fees:
    // { 'OF6L2D-6LIKD-4OOHR7':
    //   { refid: null,
    //     userref: 0,
    //     status: 'closed',
    //     reason: null,
    //     opentm: 1530694339.8116,
    //     closetm: 1530694402.9572,
    //     starttm: 0,
    //     expiretm: 0,
    //     descr: [Object],
    //     vol: '0.00500000',
    //     vol_exec: '0.00500000',
    //     cost: '27.9',
    //     fee: '0',
    //     price: '5595.0',
    //     stopprice: '0.00000',
    //     limitprice: '0.00000',
    //     misc: '',
    //     oflags: 'fciq' } } }

    callback(undefined, {price, amount, date});
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('getOrder', cb), true);
  retry(null, fetch, handle);
}

Trader.prototype.checkOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const result = data.result[order];

    callback(undefined, {
      executed: result.vol === result.vol_exec,
      open: result.status === 'open',
      filledAmount: +result.vol_exec
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb));
  retry(null, fetch, handle);
};

Trader.prototype.cancelOrder = function(order, callback) {
  const reqData = {txid: order};

  const handle = (err, data) => {
    if(err) {
      if(err.message.includes('Unknown order')) {
        return callback(undefined, true);
      }

      if(includes(err.message, unknownResultErrors)) {
        console.log(new Date, 'CANCEL UNKOWN RESULT');
        setTimeout(() => {
          //TEMP
          const handle = (err, data) => {
            if(err) return callback(err);
            console.log('TEMPTEMPTEMP', data);
          };

          const reqData = {txid: order};

          const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb));
          retry(null, fetch, handle);
        }, 1000)
        //TEMP
        return this.getRawOpenOrders((err2, orders) => {
          if(err2) {
            return callback(err2);
          }

          const exists = _.find(orders, (o, id) => {
            order === id;
          });

          if(exists) {
            // the cancel did not come through
            // naive retry
            console.log(new Date, 'it still exists, retrying cancel');
            return this.cancelOrder(order, callback);
          }

          console.log(new Date, 'it was cancelled');
          return callback(undefined, true);
        });
      }

      return callback(err)
    }

    console.log(data);

    callback(undefined, false);
  }

  const fetch = cb => this.kraken.api('CancelOrder', reqData, this.handleResponse('cancelOrder', cb));
  retry(null, fetch, handle);
};

Trader.prototype.getRawOpenOrders = function(callback) {
  const handle = (err, data) => {
    if(err) {
      return callback(err)
    }

    callback(undefined, data.result.open);
  }

  const fetch = cb => this.kraken.api('OpenOrders', {}, this.handleResponse('getOpenOrders', cb));
  retry(null, fetch, handle);
}

Trader.prototype.getOpenOrders = function(callback) {

  this.getRawOpenOrders((err, allOrders) => {
    if(err) {
      console.log(err);

      return callback(err)
    }

    const orders = [];

    _.each(allOrders, (o, id) => {
      if(o.descr.pair === this.pair) {
        orders.push(id);
      }
    });

    callback(undefined, orders);
  });
}

Trader.getCapabilities = function () {
  return {
    name: 'Kraken',
    slug: 'kraken',
    currencies: marketData.currencies,
    assets: marketData.assets,
    markets: marketData.markets,
    requires: ['key', 'secret'],
    providesHistory: 'date',
    providesFullHistory: true,
    tid: 'date',
    tradable: true,
    gekkoBroker: 0.6
  };
}

module.exports = Trader;
<commit_msg>rewrite unknown result logic<commit_after>const Kraken = require('kraken-api');
const moment = require('moment');
const _ = require('lodash');
const retry = require('../exchangeUtils').retry;

const marketData = require('./kraken-markets.json');

const Trader = function(config) {
  _.bindAll(this);

  if(_.isObject(config)) {
    this.key = config.key;
    this.secret = config.secret;
    this.currency = config.currency.toUpperCase()
    this.asset = config.asset.toUpperCase();
  }

  this.name = 'kraken';
  this.since = null;

  this.market = _.find(Trader.getCapabilities().markets, (market) => {
    return market.pair[0] === this.currency && market.pair[1] === this.asset
  });
  this.pair = this.market.book;

  this.kraken = new Kraken(
    this.key,
    this.secret,
    {timeout: +moment.duration(60, 'seconds')}
  );
}

const recoverableErrors = [
  'SOCKETTIMEDOUT',
  'TIMEDOUT',
  'CONNRESET',
  'CONNREFUSED',
  'NOTFOUND',
  'API:Rate limit exceeded',
  'Service:Unavailable',
  'Request timed out',
  'Empty response',
  'API:Invalid nonce',
  'General:Temporary lockout',
  'Response code 520',
  'Response code 525'
];

// errors that might mean
// the API call succeeded.
const unknownResultErrors = [
  'Response code 502',
  'Response code 504'
]

const includes = (str, list) => {
  if(!_.isString(str))
    return false;

  return _.some(list, item => str.includes(item));
}

Trader.prototype.handleResponse = function(funcName, callback, nonMutating, payload) {
  return (error, body) => {

    if(!error && !body) {
      error = new Error('Empty response');
    }

    if(error) {
      if(includes(error.message, recoverableErrors)) {
        error.notFatal = true;
      }

      if(includes(error.message, ['Rate limit exceeded'])) {
        error.notFatal = true;
        error.backoffDelay = 1000;
      }

      if(nonMutating && includes(error.message, unknownResultErrors)) {
        // this call only tried to retrieve data, safe to redo
        error.notFatal = true;
      }

      if(funcName === 'addOrder' && includes(error.message, unknownResultErrors)) {
        console.log(new Date, 'ADD ORDER UNKOWN RESULT');

        const { tradeType, amount, price } = payload;

        return this.getRawOpenOrders((err2, orders) => {
          if(err2) {
            console.log('err2', err2);
            return callback(err2);
          }

          _.each(orders, (o, id) => {
            o.id = id;
          });

          const order = _.find(orders, o => {
            if(o.descr.type !== tradeType) {
              return false;
            }

            const ts = moment.unix((o.opentm + '').split('.')[0])
            if(moment().diff(ts, 'm') > 10) {
              return false;
            }

            // string vs float
            if(o.descr.price != price) {
              console.log(new Date, 'ADD ORDER UNKOWN RESULT, price descrep:', order);
              return false;
            }

            // string vs float
            if(o.vol != amount) {
              console.log(new Date, 'ADD ORDER UNKOWN RESULT, amount descrep:', order);
              return false;
            }

            return true;
          });

          if(!order) {
            return callback(err);
          }

          return callback(undefined, { catched: true, id: order.id });
        });
      }

      if(funcName === 'cancelOrder' && includes(error.message, unknownResultErrors)) {
        console.log('broken cancel');

        return setTimeout(() => {
          const handle = (err2, data) => {
            if(err2) {
              cosnole.log('err2', err2);
              return callback(err2);
            }

            const order = _.get(data, `result["${order}"]`);

            if(!_.isObject(order)) {
              console.log('refefetched broken cancel, cannot find order...', data);
              throw 'a';
            }

            if(order.status === 'canceled') {
              console.log(new Date, 'it still exists, retrying cancel');
              return this.cancelOrder(order, callback);
            }

            console.log(new Date, 'it was canceled');
            return callback(undefined, true, { catched: true, filled: parseFloat(vol_exec) });

          };

          const reqData = {txid: payload};

          const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb));
          retry(null, fetch, handle);
        }, 1000)
      }

      return callback(error);
    }

    return callback(undefined, body);
  }
};

Trader.prototype.getTrades = function(since, callback, descending) {
  const startTs = since ? moment(since).valueOf() : null;

  const handle = (err, trades) => {
    if (err) return callback(err);

    var parsedTrades = [];
    _.each(trades.result[this.pair], function(trade) {
      // Even when you supply 'since' you can still get more trades than you asked for, it needs to be filtered
      if (_.isNull(startTs) || startTs < moment.unix(trade[2]).valueOf()) {
        parsedTrades.push({
          tid: moment.unix(trade[2]).valueOf() * 1000000,
          date: parseInt(Math.round(trade[2]), 10),
          price: parseFloat(trade[0]),
          amount: parseFloat(trade[1])
        });
      }
    }, this);

    if(descending)
      callback(undefined, parsedTrades.reverse());
    else
      callback(undefined, parsedTrades);
  };

  const reqData = {
    pair: this.pair
  };

  if(since) {
    // Kraken wants a tid, which is found to be timestamp_ms * 1000000 in practice. No clear documentation on this though
    reqData.since = startTs * 1000000;
  }

  const fetch = cb => this.kraken.api('Trades', reqData, this.handleResponse('getTrades', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.getPortfolio = function(callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const assetAmount = parseFloat( data.result[this.market.prefixed[1]] );
    const currencyAmount = parseFloat( data.result[this.market.prefixed[0]] );

    if(!_.isNumber(assetAmount) || _.isNaN(assetAmount)) {
      console.log(`Kraken did not return portfolio for ${this.asset}, assuming 0.`);
      assetAmount = 0;
    }

    if(!_.isNumber(currencyAmount) || _.isNaN(currencyAmount)) {
      console.log(`Kraken did not return portfolio for ${this.currency}, assuming 0.`);
      currencyAmount = 0;
    }

    const portfolio = [
      { name: this.asset, amount: assetAmount },
      { name: this.currency, amount: currencyAmount }
    ];

    return callback(undefined, portfolio);
  };

  const fetch = cb => this.kraken.api('Balance', {}, this.handleResponse('getPortfolio', cb, true));
  retry(null, fetch, handle);
};

// This assumes that only limit orders are being placed with standard assets pairs
// It does not take into account volume discounts.
// Base maker fee is 0.16%, taker fee is 0.26%.
Trader.prototype.getFee = function(callback) {
  const makerFee = 0.16;
  callback(undefined, makerFee / 100);
};

Trader.prototype.getTicker = function(callback) {
  const handle = (err, data) => {
    if (err) return callback(err);

    const result = data.result[this.pair];
    const ticker = {
      ask: result.a[0],
      bid: result.b[0]
    };
    callback(undefined, ticker);
  };

  const reqData = {pair: this.pair}
  const fetch = cb => this.kraken.api('Ticker', reqData, this.handleResponse('getTicker', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.roundAmount = function(amount) {
  return _.floor(amount, this.market.amountPrecision);
};

Trader.prototype.roundPrice = function(amount) {
  return _.round(amount, this.market.pricePrecision);
};

Trader.prototype.addOrder = function(tradeType, amount, price, callback) {
  price = this.roundAmount(price); // only round price, not amount

  const handle = (err, data) => {
    if(err) {

      return callback(err);
    }

    let txid;

    if(data.catched) {
      txid = data.id;
    } else {
      txid = data.result.txid[0];
    }

    callback(undefined, txid);
  };

  const reqData = {
    pair: this.pair,
    type: tradeType.toLowerCase(),
    ordertype: 'limit',
    price: price,
    volume: amount
  };

  const fetch = cb => this.kraken.api('AddOrder', reqData, this.handleResponse('addOrder', cb, false, { tradeType, amount, price }));
  retry(null, fetch, handle);
};

Trader.prototype.buy = function(amount, price, callback) {
  this.addOrder('buy', amount, price, callback);
};

Trader.prototype.sell = function(amount, price, callback) {
  this.addOrder('sell', amount, price, callback);
};

Trader.prototype.getOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const price = parseFloat( data.result[ order ].price );
    const amount = parseFloat( data.result[ order ].vol_exec );
    const date = moment.unix( data.result[ order ].closetm );

    // TODO: figure out fees, kraken is reporting 0 fees:
    // { 'OF6L2D-6LIKD-4OOHR7':
    //   { refid: null,
    //     userref: 0,
    //     status: 'closed',
    //     reason: null,
    //     opentm: 1530694339.8116,
    //     closetm: 1530694402.9572,
    //     starttm: 0,
    //     expiretm: 0,
    //     descr: [Object],
    //     vol: '0.00500000',
    //     vol_exec: '0.00500000',
    //     cost: '27.9',
    //     fee: '0',
    //     price: '5595.0',
    //     stopprice: '0.00000',
    //     limitprice: '0.00000',
    //     misc: '',
    //     oflags: 'fciq' } } }

    callback(undefined, {
      price,
      amount,
      date,
      feePercent: 0.16 // default for now
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('getOrder', cb, true));
  retry(null, fetch, handle);
}

Trader.prototype.checkOrder = function(order, callback) {
  const handle = (err, data) => {
    if(err) return callback(err);

    const result = data.result[order];

    callback(undefined, {
      executed: result.vol === result.vol_exec,
      open: result.status === 'open',
      filledAmount: +result.vol_exec
    });
  };

  const reqData = {txid: order};

  const fetch = cb => this.kraken.api('QueryOrders', reqData, this.handleResponse('checkOrder', cb, true));
  retry(null, fetch, handle);
};

Trader.prototype.cancelOrder = function(order, callback) {
  const reqData = {txid: order};

  const handle = (err, data) => {
    if(err) {
      if(err.message.includes('Unknown order')) {
        return callback(undefined, true);
      }

      return callback(err)
    }

    if(data.filled) {
      callback(undefined, false, data);
    }

    callback(undefined, false);
  }

  const fetch = cb => this.kraken.api('CancelOrder', reqData, this.handleResponse('cancelOrder', cb, false, order));
  retry(null, fetch, handle);
};

Trader.prototype.getRawOpenOrders = function(callback) {
  const handle = (err, data) => {
    if(err) {
      return callback(err)
    }

    callback(undefined, data.result.open);
  }

  const fetch = cb => this.kraken.api('OpenOrders', {}, this.handleResponse('getOpenOrders', cb, true));
  retry(null, fetch, handle);
}

Trader.prototype.getOpenOrders = function(callback) {

  this.getRawOpenOrders((err, allOrders) => {
    if(err) {
      console.log(err);

      return callback(err)
    }

    const orders = [];

    _.each(allOrders, (o, id) => {
      if(o.descr.pair === this.pair) {
        orders.push(id);
      }
    });

    callback(undefined, orders);
  });
}

Trader.getCapabilities = function () {
  return {
    name: 'Kraken',
    slug: 'kraken',
    currencies: marketData.currencies,
    assets: marketData.assets,
    markets: marketData.markets,
    requires: ['key', 'secret'],
    providesHistory: 'date',
    providesFullHistory: true,
    tid: 'date',
    tradable: true,
    gekkoBroker: 0.6
  };
}

module.exports = Trader;
<|endoftext|>


<commit_before>'use strict';

function CodeCtrl($scope, $http, $location, $timeout) {
  $(document).keydown(function(event) {
    var e = window.event || event;
    if (e.keyCode == 13 && e.shiftKey) {  // shift-enter
      $scope.runCode($scope.code);
    } else if (e.keyCode == 33) {  // page up
      $scope.location.path("/" + $scope.prevChapter());
      $scope.$apply();
      return false;
    } else if (e.keyCode == 34) {  // page down
      $scope.location.path("/" + $scope.nextChapter());
      $scope.$apply();
      return false;
    }
  });

  $scope.location = $location;

  // This naively assumes that the dirty state is "sticky" unless you force a
  // full recomputation. Works for most purposes in the UI.
  $scope._dirty = false;
  $scope.dirty = function(force_recompute) {
    if (force_recompute || !$scope._dirty) {
      $scope._dirty = ($scope.tutorial != undefined &&
                       $scope.code != $scope.tutorial.code);
    }
    return $scope._dirty;
  };

  $scope.storageKey = function() {
    return "pytut-" + $scope.tutorial.name;
  };

  $scope.saveCode = function() {
    if ($scope.tutorial === undefined) {
      return;
    }
    var dirty = $scope.dirty(true);

    // Save to the internal JS cache first.
    $scope.tutorial.userCode = (dirty) ? $scope.code : undefined;

    // Also save to HTML5 local storage if possible.
    // This protects users against refresh and browser crashes.
    if (typeof(Storage) != "undefined") {
      if ($scope.tutorial.userCode === undefined) {
        localStorage.removeItem($scope.storageKey());
      } else {
        localStorage[$scope.storageKey()] = JSON.stringify({
          time_ms: +new Date(),
          user_code: $scope.tutorial.userCode,
        });
      }
    }
  };

  $scope.loadCode = function() {
    if ($scope.tutorial === undefined) {
      return;
    }

    // Get from localStorage if possible.
    if (typeof(Storage) != "undefined") {
      var val = localStorage[$scope.storageKey()];
      if (val !== undefined) {
        // TODO: Age out old things?
        $scope.tutorial.userCode = JSON.parse(val).user_code;
      }
    }
    // If there is data in userCode now, then we display that. Otherwise we get
    // the tutorial code and display that.
    if ($scope.tutorial.userCode !== undefined) {
      $scope.code = $scope.tutorial.userCode;
    } else {
      $scope.code = $scope.tutorial.code;
    }
    $scope.dirty(true);  // Force dirty bit recomputation.
  };
  // TOC should be off when we start.
  $scope.tocShowing = false;

  $scope.parseTutorial = function(text) {
    var lines = text.split(/\r\n|\n|\r/);
    // If the file starts with comments right at the beginning, strip them off
    // (typically directives to editors).
    var cur = 0;
    for (; cur<lines.length; cur++) {
      if (lines[cur].trim() && lines[cur].search(/^\s*#.*$/) < 0) {
        break;
      }
    }
    // Remaining text is treated as a whole.
    text = lines.slice(cur).join('\n');
    var groups = text.match(/\s*"""([^]*?)\n\s*([^]*?)"""\s+([^]*)$/m)
    if (!groups) {
      return {title: "", description: "", code: ""};
    }

    return {
      title: groups[1].replace(/\\(["'])/g, '$1'),
      description: groups[2].replace(/\\(["'])/g, '$1'),
      code: groups[3].replace(/^(\s*)__doc__\s*=\s*/, '$1'),
    };
  };

  var tutorialsPending = -1;

  function loadTutorial(index, path) {
    $http.get(path).
      success(function(text) {
        var parsed = $scope.parseTutorial(text);
        $scope.tutorials[index] = {
          name: name,
          index: index,
          title: parsed.title,
          description: parsed.description,
          code: parsed.code,
        };
        tutorialsPending--;
      });
  }

  $http.get("tutorials/tutorials.json").
    success(function(tutNames) {
      $scope.tutorials = new Array(tutNames.length);
      tutorialsPending = tutNames.length;

      for (var i=0; i<tutNames.length; i++) {
        loadTutorial(i, "tutorials/" + tutNames[i] + ".py");
      }

      // Triggered (below) when no more tutorials are pending.
      var onTutorialsLoaded = function() {
        $scope.tutorial = $scope.tutorials[$scope.chapter-1];
        $scope.loadCode();

        // Redirect to the first page if none is specified.
        if (!$location.path()) {
          $location.path('/1').replace();
        }

        // Notice when the path changes and use that to
        // navigate, but only after we actually have
        // data.
        $scope.$watch('location.path()', function(path) {
          var newChapter = +path.replace(/^[/]/, '');
          if (newChapter == 0) {
            // Special value - don't go to chapter 0.
            newChapter = $scope.chapter;
            $scope.location.path("/" + $scope.chapter).replace();
          }
          $scope.tocShowing = false;
          if (newChapter != $scope.chapter) {
            $scope.saveCode();
            $scope.chapter = newChapter;
            $scope.tutorial = $scope.tutorials[newChapter-1];
            $scope.loadCode();
            $scope.clearOutput();
            $(document.body).scrollTop(0);
          }
        });
      };

      // Finally, we wait until tutorialsPending is zero.
      var checkPending = function() {
        if (tutorialsPending > 0) {
          window.setTimeout(checkPending, 1);
        } else {
          onTutorialsLoaded();
        }
      };
      window.setTimeout(checkPending, 1);
    });

  // Set up a handy timer that watches when _time changes and starts a new
  // timeout to change it. It's a bit more elegant than creating a function
  // and creating a timeout from a timeout.
  $scope._time = new Date();
  $scope.$watch('_time', function() {
    $scope.saveCode();  // Also forces dirty bit recomputation.
    $timeout(function(){
      $scope._time = new Date();
    }, 5000);
  });

  $scope.addOutputText = function(text) {
    $scope._addText(text, "stdout");
  };

  $scope.addErrorText = function(text) {
    $scope._addText(text, "stderr");
  };

  $scope._addText = function(text, elementClass) {
    var output = document.getElementById("output");
    var scrolled = output.scrollHeight - output.clientHeight - output.scrollTop;
    var scrollDown = scrolled < 12;
    var pre = document.createElement("pre");
    pre.className = elementClass;
    pre.appendChild(document.createTextNode(text));
    output.appendChild(pre);
    if (scrollDown) {
      output.scrollTop = output.scrollHeight - output.clientHeight;
    }
  };

  // Creates a function that accepts code and runs it on the server.
  function makeRunOnServer() {
    return function(code) {
      $scope.clearOutput();
      $http.post("/runcode", code).success(function(data) {
        $scope.addOutputText(data.stdout);
        $scope.addErrorText(data.stderr);
      });
    };
  }

  // Creates a function that runs code on PyPy.js.
  // onLoaded is called when the vm has finished initializing.
  function makeRunOnPyPyJS(onLoaded) {
    // Initialize PyPy.js
    var vm = new PyPyJS({
      stdout: $scope.addOutputText,
      stderr: $scope.addErrorText,
      autoLoadModules: false,
    });
    vm.ready.then(onLoaded);
    return function(code) {
      if (!$scope.vmLoaded) {
        console.log("vm not ready - ignoring request to run code")
        return;
      }
      $scope.clearOutput();
      try {
        vm.loadModuleData("contextlib").then(function() {
          // Clean up the global namespace, get the help function, and create a doctest.
          vm.execfile("../../../../../_preamble.py").then(function() {
            return vm.exec(code).catch(function(err) {
              console.log(err);
              $scope.addErrorText(err.trace);
            });
          });
        });
      } catch (err) {
        console.log(err);
        $scope.addErrorText("Internal Error: " + $scope.prettyError(err));
      }
    };
  }

  // prettyError is used to make javascript exceptions easier on the eyes.
  $scope.prettyError = function(err) {
    // If we are dealing with Firefox, just output the message.
    if (err.stack[0] == '@') {
      return err.name + ": " + err.message;
    }
    var lines = err.stack.split(/\r\n|\r|\n/);
    var output = [lines[0]];
    // Now only keep lines that have <anonymous> as the file name, and filter
    // out irrelevant text from those.
    for (var i = 1; i < lines.length; i++) {
      var line = lines[i];
      if (line.match(/^\s*at .*runCode/)) {
        break;
      }
      line = line.replace(/\([^()]*\)/, '');
      line = line.replace(/\s*\(.*:(\d+):(\d+)\)$/, ":$1:$2");
      line = line.replace("Object.eval", "_Main_");
      output.push(line);
    }
    return output.join("\n");
  };

  // If PyPyJS loaded, we can use that. Otherwise, run this thing on the server.
  (function(document, window, undefined) {
    $scope.vmLoaded = false;

    if (window.PyPyJS !== undefined) {
      console.log("using client-side PyPy.js implementation");
      $scope.runCode = makeRunOnPyPyJS(function() {
        console.log("vm loaded");
        $scope.vmLoaded = true;
      });
    } else {
      console.log("using server-side Python implementation");
      $scope.runCode = makeRunOnServer();
      $scope.vmLoaded = true;
    }
  }(document, window))

  // This is useful for binding keys in the code window to do
  // nothing at all. We can't, for whatever reason, define this
  // function in-line.
  //
  // Note that we use this for Shift-Enter because it is a
  // document-wide keystroke that should run the code, but if
  // you're in the code window it causes a newline to be
  // inserted, too.
  $scope.doNothing = function(e) {}

  $scope.clearOutput = function() {
    var output = document.getElementById("output");
    while (output.lastChild) {
      output.removeChild(output.lastChild);
    }
  }

  $scope.clearCode = function() {
    $scope.code = "";
  }

  $scope.revertCode = function() {
    $scope.code = $scope.tutorial.code;
    // Force dirty bit recomputation:
    $scope.dirty(true);
  };

  $scope.revertAll = function() {
    // Remove all local storage if we have it.
    if (typeof(Storage) != "undefined") {
      localStorage.clear();
    }
    $scope.revertCode();
  };

  $scope.prevChapter = function() {
    if ($scope.tutorial == undefined || $scope.tutorial.index <= 0) {
      return 0;  // special value meaning don't go there.
    }
    return $scope.tutorial.index;  // chapter - 1
  }

  $scope.nextChapter = function() {
    if ($scope.tutorial == undefined ||
        $scope.tutorial.index >= $scope.tutorials.length - 1) {
      return 0;  // special value - don't go there.
    }
    return $scope.tutorial.index + 2;  // chapter + 1
  }
}
<commit_msg>Fixed broken slide loading.<commit_after>'use strict';

function CodeCtrl($scope, $http, $location, $timeout) {
  $(document).keydown(function(event) {
    var e = window.event || event;
    if (e.keyCode == 13 && e.shiftKey) {  // shift-enter
      $scope.runCode($scope.code);
    } else if (e.keyCode == 33) {  // page up
      $scope.location.path("/" + $scope.prevChapter());
      $scope.$apply();
      return false;
    } else if (e.keyCode == 34) {  // page down
      $scope.location.path("/" + $scope.nextChapter());
      $scope.$apply();
      return false;
    }
  });

  $scope.location = $location;

  // This naively assumes that the dirty state is "sticky" unless you force a
  // full recomputation. Works for most purposes in the UI.
  $scope._dirty = false;
  $scope.dirty = function(force_recompute) {
    if (force_recompute || !$scope._dirty) {
      $scope._dirty = ($scope.tutorial != undefined &&
                       $scope.code != $scope.tutorial.code);
    }
    return $scope._dirty;
  };

  $scope.storageKey = function() {
    return "pytut-" + $scope.tutorial.name;
  };

  $scope.saveCode = function() {
    if ($scope.tutorial === undefined) {
      return;
    }
    var dirty = $scope.dirty(true);

    // Save to the internal JS cache first.
    $scope.tutorial.userCode = (dirty) ? $scope.code : undefined;

    // Also save to HTML5 local storage if possible.
    // This protects users against refresh and browser crashes.
    if (typeof(Storage) != "undefined") {
      if ($scope.tutorial.userCode === undefined) {
        localStorage.removeItem($scope.storageKey());
      } else {
        localStorage[$scope.storageKey()] = JSON.stringify({
          time_ms: +new Date(),
          user_code: $scope.tutorial.userCode,
        });
      }
    }
  };

  $scope.loadCode = function() {
    if ($scope.tutorial === undefined) {
      return;
    }

    // Get from localStorage if possible.
    if (typeof(Storage) != "undefined") {
      var val = localStorage[$scope.storageKey()];
      if (val !== undefined) {
        // TODO: Age out old things?
        $scope.tutorial.userCode = JSON.parse(val).user_code;
      }
    }
    // If there is data in userCode now, then we display that. Otherwise we get
    // the tutorial code and display that.
    if ($scope.tutorial.userCode !== undefined) {
      $scope.code = $scope.tutorial.userCode;
    } else {
      $scope.code = $scope.tutorial.code;
    }
    $scope.dirty(true);  // Force dirty bit recomputation.
  };
  // TOC should be off when we start.
  $scope.tocShowing = false;

  $scope.parseTutorial = function(text) {
    var lines = text.split(/\r\n|\n|\r/);
    // If the file starts with comments right at the beginning, strip them off
    // (typically directives to editors).
    var cur = 0;
    for (; cur<lines.length; cur++) {
      if (lines[cur].trim() && lines[cur].search(/^\s*#.*$/) < 0) {
        break;
      }
    }
    // Remaining text is treated as a whole.
    text = lines.slice(cur).join('\n');
    var groups = text.match(/\s*"""([^]*?)\n\s*([^]*?)"""\s+([^]*)$/m)
    if (!groups) {
      return {title: "", description: "", code: ""};
    }

    return {
      title: groups[1].replace(/\\(["'])/g, '$1'),
      description: groups[2].replace(/\\(["'])/g, '$1'),
      code: groups[3].replace(/^(\s*)__doc__\s*=\s*/, '$1'),
    };
  };

  var tutorialsPending = -1;

  function loadTutorial(index, name) {
    var path = "tutorials/" + name + ".py"
    $http.get(path).
      success(function(text) {
        var parsed = $scope.parseTutorial(text);
        $scope.tutorials[index] = {
          name: name,
          index: index,
          title: parsed.title,
          description: parsed.description,
          code: parsed.code,
        };
        tutorialsPending--;
      });
  }

  $http.get("tutorials/tutorials.json").
    success(function(tutNames) {
      $scope.tutorials = new Array(tutNames.length);
      tutorialsPending = tutNames.length;

      for (var i=0; i<tutNames.length; i++) {
        loadTutorial(i, tutNames[i]);
      }

      // Triggered (below) when no more tutorials are pending.
      var onTutorialsLoaded = function() {
        $scope.tutorial = $scope.tutorials[$scope.chapter-1];
        $scope.loadCode();

        // Redirect to the first page if none is specified.
        if (!$location.path()) {
          $location.path('/1').replace();
        }

        // Notice when the path changes and use that to
        // navigate, but only after we actually have
        // data.
        $scope.$watch('location.path()', function(path) {
          var newChapter = +path.replace(/^[/]/, '');
          if (newChapter == 0) {
            // Special value - don't go to chapter 0.
            newChapter = $scope.chapter;
            $scope.location.path("/" + $scope.chapter).replace();
          }
          $scope.tocShowing = false;
          if (newChapter != $scope.chapter) {
            $scope.saveCode();
            $scope.chapter = newChapter;
            $scope.tutorial = $scope.tutorials[newChapter-1];
            $scope.loadCode();
            $scope.clearOutput();
            $(document.body).scrollTop(0);
          }
        });
      };

      // Finally, we wait until tutorialsPending is zero.
      var checkPending = function() {
        if (tutorialsPending > 0) {
          window.setTimeout(checkPending, 1);
        } else {
          onTutorialsLoaded();
        }
      };
      window.setTimeout(checkPending, 1);
    });

  // Set up a handy timer that watches when _time changes and starts a new
  // timeout to change it. It's a bit more elegant than creating a function
  // and creating a timeout from a timeout.
  $scope._time = new Date();
  $scope.$watch('_time', function() {
    $scope.saveCode();  // Also forces dirty bit recomputation.
    $timeout(function(){
      $scope._time = new Date();
    }, 5000);
  });

  $scope.addOutputText = function(text) {
    $scope._addText(text, "stdout");
  };

  $scope.addErrorText = function(text) {
    $scope._addText(text, "stderr");
  };

  $scope._addText = function(text, elementClass) {
    var output = document.getElementById("output");
    var scrolled = output.scrollHeight - output.clientHeight - output.scrollTop;
    var scrollDown = scrolled < 12;
    var pre = document.createElement("pre");
    pre.className = elementClass;
    pre.appendChild(document.createTextNode(text));
    output.appendChild(pre);
    if (scrollDown) {
      output.scrollTop = output.scrollHeight - output.clientHeight;
    }
  };

  // Creates a function that accepts code and runs it on the server.
  function makeRunOnServer() {
    return function(code) {
      $scope.clearOutput();
      $http.post("/runcode", code).success(function(data) {
        $scope.addOutputText(data.stdout);
        $scope.addErrorText(data.stderr);
      });
    };
  }

  // Creates a function that runs code on PyPy.js.
  // onLoaded is called when the vm has finished initializing.
  function makeRunOnPyPyJS(onLoaded) {
    // Initialize PyPy.js
    var vm = new PyPyJS({
      stdout: $scope.addOutputText,
      stderr: $scope.addErrorText,
      autoLoadModules: false,
    });
    vm.ready.then(onLoaded);
    return function(code) {
      if (!$scope.vmLoaded) {
        console.log("vm not ready - ignoring request to run code")
        return;
      }
      $scope.clearOutput();
      vm.loadModuleData("contextlib").then(function() {
        try {
          // Clean up the global namespace, get the help function, and create a doctest.
          vm.execfile("../../../../../_preamble.py").then(function() {
            return vm.exec(code).catch(function(err) {
              console.log(err);
              $scope.addErrorText(err.trace);
            });
          });
        } catch (err) {
          console.log(err);
          $scope.addErrorText("Internal Error: " + $scope.prettyError(err));
        }
      });
    };
  }

  // prettyError is used to make javascript exceptions easier on the eyes.
  $scope.prettyError = function(err) {
    // If we are dealing with Firefox, just output the message.
    if (err.stack[0] == '@') {
      return err.name + ": " + err.message;
    }
    var lines = err.stack.split(/\r\n|\r|\n/);
    var output = [lines[0]];
    // Now only keep lines that have <anonymous> as the file name, and filter
    // out irrelevant text from those.
    for (var i = 1; i < lines.length; i++) {
      var line = lines[i];
      if (line.match(/^\s*at .*runCode/)) {
        break;
      }
      line = line.replace(/\([^()]*\)/, '');
      line = line.replace(/\s*\(.*:(\d+):(\d+)\)$/, ":$1:$2");
      line = line.replace("Object.eval", "_Main_");
      output.push(line);
    }
    return output.join("\n");
  };

  // If PyPyJS loaded, we can use that. Otherwise, run this thing on the server.
  (function(document, window, undefined) {
    $scope.vmLoaded = false;

    if (window.PyPyJS !== undefined) {
      console.log("using client-side PyPy.js implementation");
      $scope.runCode = makeRunOnPyPyJS(function() {
        console.log("vm loaded");
        $scope.vmLoaded = true;
      });
    } else {
      console.log("using server-side Python implementation");
      $scope.runCode = makeRunOnServer();
      $scope.vmLoaded = true;
    }
  }(document, window))

  // This is useful for binding keys in the code window to do
  // nothing at all. We can't, for whatever reason, define this
  // function in-line.
  //
  // Note that we use this for Shift-Enter because it is a
  // document-wide keystroke that should run the code, but if
  // you're in the code window it causes a newline to be
  // inserted, too.
  $scope.doNothing = function(e) {}

  $scope.clearOutput = function() {
    var output = document.getElementById("output");
    while (output.lastChild) {
      output.removeChild(output.lastChild);
    }
  }

  $scope.clearCode = function() {
    $scope.code = "";
  }

  $scope.revertCode = function() {
    $scope.code = $scope.tutorial.code;
    // Force dirty bit recomputation:
    $scope.dirty(true);
  };

  $scope.revertAll = function() {
    // Remove all local storage if we have it.
    if (typeof(Storage) != "undefined") {
      localStorage.clear();
    }
    $scope.revertCode();
  };

  $scope.prevChapter = function() {
    if ($scope.tutorial == undefined || $scope.tutorial.index <= 0) {
      return 0;  // special value meaning don't go there.
    }
    return $scope.tutorial.index;  // chapter - 1
  }

  $scope.nextChapter = function() {
    if ($scope.tutorial == undefined ||
        $scope.tutorial.index >= $scope.tutorials.length - 1) {
      return 0;  // special value - don't go there.
    }
    return $scope.tutorial.index + 2;  // chapter + 1
  }
}
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Lint detector for invalid text.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
module.exports = function( context ) {
  'use strict';

  var badTexts = [
    'tool' + 'Box', // intentionally split to avoid getting caught in the lint, split manually because lint apparently rules cannot be disabled in their declarations
    'Tool' + 'Box', // intentionally split to avoid getting caught in the lint, split manually because lint apparently rules cannot be disabled in their declarations
    'Check' + 'Box', // intentionally split to avoid getting caught in the lint, split manually because lint apparently rules cannot be disabled in their declarations
    'check' + 'Box' // intentionally split to avoid getting caught in the lint, split manually because lint apparently rules cannot be disabled in their declarations
  ];

  var checkNode = function( node, text ) {
    badTexts.forEach( function( badText ) {
      if ( text.indexOf( badText ) >= 0 ) {
        context.report( {
          node: node,
          message: 'Bad text: ' + badText + ' in ' + node.type
        } );
      }
    } );
  };
  return {
    Identifier: function checkCopyright( node ) {
      checkNode( node, node.name );
    },
    Literal: function checkLiteral( node ) {
      checkNode( node, node.raw );
    }
  };
};

module.exports.schema = [
  // JSON Schema for rule options goes here
];<commit_msg>Converted to use full Program text and eslint-disable, and added rule forbidding 'extends Object', see https://github.com/phetsims/chipper/issues/703<commit_after>// Copyright 2018, University of Colorado Boulder
/* eslint-disable */

/**
 * Lint detector for invalid text.  Checks the entire file and does not correctly report line number.
 * Lint is disabled for this file so the bad texts aren't themselves flagged.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
module.exports = function( context ) {
  'use strict';

  var badTexts = [
    'toolBox',
    'ToolBox',
    'CheckBox',
    'checkBox',
    'extends Object'
  ];

  return {
    Program: function( node ) {
      var sourceCode = context.getSourceCode();
      var text = sourceCode.text;
      badTexts.forEach( function( badText ) {
        if ( text.indexOf( badText ) >= 0 ) {
          context.report( {
            node: node,
            message: 'File contains bad text: \'' + badText + '\''
          } );
        }
      } )
    }
  };
};

module.exports.schema = [
  // JSON Schema for rule options goes here
];<|endoftext|>