<commit_before>/*
* jQuery TabaKordion - v1.0.0
* A fully accessible to WAI specification; tabs and accordion jQuery plugin.
* http://www.tariqkhan.co.uk/
*
* Made by Tariq Khan
* Under MIT License
*/
;(function($, window, document, undefined) {
	var	pluginName = 'TabaKordion',
		defaults = {
			accordion:	true,
			openFirst:	true
		};
	function Plugin(element, options) {
		this.element = element;
		this.options = $.extend( {}, defaults, options);
		this._defaults = defaults;
		this._name = pluginName;
		this.$TabaKordion = $(element);
		this.$tabs = this.$TabaKordion.find('.tab');
		this.$panels = this.$TabaKordion.children('.panel');
		if(this.$TabaKordion.hasClass('tabs')) {
			this.options.accordion = false;
		}
		this.keys = new keyCodes();
		this.bindEventHandlers();
		this.init();
	}
	// Object defining names for keycodes
	function keyCodes() {
		// Trigger keys
		this.tab		= 9;
		this.enter		= 13;
		this.esc		= 27;
		this.space		= 32;
		// Scroll keys
		this.pageup		= 33;
		this.pagedown	= 34;
		this.end		= 35;
		this.home		= 36;
		// Direction keys
		this.left		= 37;
		this.up			= 38;
		this.right		= 39;
		this.down		= 40;
	}
	// focusable is a jQuery extension to add :focusable selector; credit to ajpiano on jQuery forums
	// Used to get all focusable elements in a panel
	$.extend($.expr[':'], {
		focusable: function(element) {
			var	nodeName = element.nodeName.toLowerCase(),
				tabIndex = $(element).attr('tabindex');
			// Element and all its ancestors must be visible
			if(($(element)[(nodeName == 'area' ? 'parents' : 'closest')](':hidden').length) == true) {
				return false;
			}
			// If tabindex is defined its value must be greater than 0
			if(!isNaN(tabIndex) && tabIndex < 0) {
				return false;
			}
			// If element is standard form control it must not be disabled
			if(/input|select|textarea|button|object/.test(nodeName) == true) {
				return !element.disabled;
			}
			// If element is link href must be defined
			if((nodeName == 'a' ||  nodeName == 'area') == true) {
				return (element.href.length > 0);
			}
			// Unknown or non-focusable element
			return false;
		}
	});
	Plugin.prototype = {
		// Initialise TabaKordion by applying ARIA attributes and hiding all panels except class selected or first
		init: function() {
			this.$panels.attr('role', 'tabpanel').attr('aria-hidden', 'true').hide();
			this.$tabs.attr('role', 'tab').attr('aria-selected', 'false').attr('tabindex', '-1')
			if(this.options.accordion) {
				this.$tabs.attr('aria-expanded', 'false').append(' (collapsed)');
			} else {
				this.$tabs.find('a').each(function() {
					$(this).replaceWith(''+$(this).text()+'');
				});
			}
			var anchor = window.location.hash;
			if(anchor && $(anchor).length) {
				this.$tabs.removeClass('selected');
				$(anchor).addClass('selected');
			}
			var $tab = this.$tabs.filter('.selected');
			if(!$tab.length && this.options.openFirst) {
				$tab = this.$tabs.first();
			}
			$tab.addClass('selected').attr('aria-selected', 'true').attr('tabindex', '0');
			if(this.options.accordion) {
				$tab.attr('aria-expanded', 'true').find('.visually-hidden').text('(expanded)');
			}
			this.$TabaKordion.find('#'+$tab.attr('aria-controls')).show().attr('aria-hidden', 'false');
		},
		// Bind event handlers for headings and panels
		bindEventHandlers: function() {
			// Create reference so pointer can be accessed in functions with events
			var thisObj = this;
			// Bind handlers for tab headings
			this.$tabs.keydown(function(e) {
				return thisObj.tabKeyDownHandler($(this), e);
			});
			this.$tabs.click(function(e) {
				return thisObj.tabClickHandler($(this), e);
			});
			this.$tabs.focus(function(e) {
				return thisObj.tabFocusHandler($(this), e);
			});
			this.$tabs.blur(function(e) {
				return thisObj.tabBlurHandler($(this), e);
			});
			// Bind handlers for panel focusable elements
			this.$panels.keydown(function(e) {
				return thisObj.panelElementsKeyDownHandler($(this), e);
			});
			if(this.options.accordion) {
				this.$panels.click(function(e) {
					return thisObj.panelElementsClickHandler($(this), e);
				});
			}
		},
		// Display/Hide panel associated with tab header and bind/unbind keydown handler to its panel focusable elements
		togglePanel: function($tab) {
			if(!this.options.accordion) {
				this.$panels.attr('aria-hidden', 'true').hide();
			}
			$panel = this.$TabaKordion.find('#'+$tab.attr('aria-controls'));
			if($panel.attr('aria-hidden') == 'true') {
				$panel.attr('aria-hidden', 'false');
				if(this.options.accordion) {
					$tab.attr('aria-expanded', 'true').find('.visually-hidden').text('(expanded)');
					$panel.slideDown();
				} else {
					$panel.show();
				}
			} else {
				$panel.attr('aria-hidden', 'true');
				if(this.options.accordion) {
					$tab.attr('aria-expanded', 'false').find('.visually-hidden').text('(collapsed)');
					$panel.slideUp();
				} else {
					$panel.hide();
				}
			}
		},
		// Give focus to new tab header and if tabs; current panel is hidden and new panel is displayed
		switchTabs: function($curTab, $newTab) {
			$curTab.removeClass('selected focus').attr('tabindex', '-1').attr('aria-selected', 'false');
			if(!this.options.accordion) {
				this.$TabaKordion.find('#'+$curTab.attr('aria-controls')).hide().attr('aria-hidden', 'true');
				this.$TabaKordion.find('#'+$newTab.attr('aria-controls')).show().attr('aria-hidden', 'false');
			}
			$newTab.addClass('selected').attr('aria-selected', 'true').attr('tabindex', '0').focus();
		},
		// Tab heading key down event handler: return true if propagating, false if consuming event
		tabKeyDownHandler: function($tab, e) {
			if(e.altKey) {
				return true;
			}
			switch(e.keyCode) {
				case this.keys.enter:
				case this.keys.space: {
					if(this.options.accordion) {
						this.togglePanel($tab);
						e.stopPropagation();
						return false;
					}
					return true;
				}
				case this.keys.left:
				case this.keys.up: {
					if(e.ctrlKey) { // Ctrl+arrow moves focus from panel elements to open tab header
					} else {
						var	tabIndex = this.$tabs.index($tab),
							// If first tab new tab is last tab else new tab is previous tab
							$newTab = tabIndex == 0 ? this.$tabs.last() : this.$tabs.eq(tabIndex - 1);
						this.switchTabs($tab, $newTab);
					}
					e.stopPropagation();
					return false;
				}
				case this.keys.right:
				case this.keys.down: {
					var	tabIndex = this.$tabs.index($tab),
						// If last tab new tab is first tab else new tab is next tab
						$newTab = tabIndex == this.$tabs.length - 1 ? this.$tabs.first() : this.$tabs.eq(tabIndex + 1);
					this.switchTabs($tab, $newTab);
					e.stopPropagation();
					return false;
				}
				case this.keys.home: {
					this.switchTabs($tab, this.$tabs.first());
					e.stopPropagation();
					return false;
				}
				case this.keys.end: {
					this.switchTabs($tab, this.$tabs.last());
					e.stopPropagation();
					return false;
				}
			}
		},
		// Tab heading click event handler
		tabClickHandler: function($tab, e) {
			$tab.addClass('selected').attr('tabindex', '0').attr('aria-selected', 'true');
			this.$tabs.not($tab).attr('tabindex', '-1').attr('aria-selected', 'false').removeClass('selected');
			this.togglePanel($tab);
			if(this.options.accordion) {
				e.stopPropagation();
				return false;
			}
			return true;
		},
		// Tab heading focus event handler
		tabFocusHandler: function($tab, e) {
			$tab.addClass('focus');
			return true;
		},
		// Tab heading blur event handler
		tabBlurHandler: function($tab, e) {
			$tab.removeClass('focus');
			return true;
		},
		// Panel key down event handler: return true if propagating else false if consuming event
		panelElementsKeyDownHandler: function($panel, e) {
			if(e.altKey) {
				return true;
			}
			switch(e.keyCode) {
				case this.keys.tab: {
					var	$focusable	= $panel.find(':focusable'),
						curIndex	= $focusable.index($(e.target)),
						panelIndex	= this.$panels.index($panel),
						numPanels	= this.$panels.length;
					if(e.shiftKey) {
						// If this is first focusable item in panel; find preceding expanded panel that has focusable items and set focus to it. Do not process if no preceding panel or focusable items
						if(curIndex == 0 && panelIndex > 0) {
							// Iterate through previous panels until we find one that is expanded and has focusable elements
							for(var index = panelIndex - 1; index >= 0; index--) {
								var	$prevPanel = this.$panels.eq(index),
									$prevTab = $('#' + $prevPanel.attr('aria-labelledby'));
								// Get the focusable items in panel
								$focusable.length = 0;
								$focusable = $prevPanel.find(':focusable');
									if($focusable.length > 0) {
									// Focusable items in panel exists; set focus to the last item
									$focusable.last().focus();
									// Reset aria-selected state of the tabs
									this.$tabs.attr('aria-selected', 'false').removeClass('selected');
									// Set associated tab's aria-selected state
									$prevTab.attr('aria-selected', 'true').addClass('selected');
									e.stopPropagation;
									return false;
								}
							}
						}
					} else if (panelIndex < numPanels) {
						// If this is the last focusable item in the panel; find nearest following expanded panel that has focusable items and set focus to it. Do not process if no preceding panel or focusable items
						if(curIndex == $focusable.length - 1) {
							// Iterate through following panels until we find one that is expanded and has focusable elements
							for(var index = panelIndex + 1; index < numPanels; index++) {
								var	$nextPanel = this.$panels.eq(index),
									$nextTab = $('#'+$nextPanel.attr('aria-labelledby'));
								// Get focusable items in panel
								$focusable.length = 0;
								$focusable = $nextPanel.find(':focusable');
								if($focusable.length > 0) {
									// There are focusable items in panel; set focus to first item
									$focusable.first().focus();
									// Reset aria-selected state of tabs
									this.$tabs.attr('aria-selected', 'false').removeClass('selected');
									// Set associated tabs aria-selected state
									$nextTab.attr('aria-selected', 'true').addClass('selected');
									e.stopPropagation;
									return false;
								}
							}
						}
					}
					break;
				}
				case this.keys.esc:
				case this.keys.left:
				case this.keys.up: {
					if(e.keyCode != this.keys.esc && !e.ctrlKey) {
						return true;
					}
					$('#'+$panel.attr('aria-labelledby')).focus();
					e.stopPropagation();
					return false;
				}
				case this.keys.pageup:
				case this.keys.pagedown: {
					if(!e.ctrlKey) {
						return true;
					}
					var	$tab = this.$tabs.filter('.selected'),
						curIndex = this.$tabs.index($tab);
					if(e.keyCode == this.keys.pageup) {
						// If first tab focus on last else focus previous tab
						var	$newTab = curIndex == 0 ? this.$tabs.last() : this.$tabs.eq(curIndex - 1);
					} else {
						// If last tab focus on first else focus on next tab
						var	$newTab = curIndex == this.$tabs.length - 1 ? this.$tabs.first() : this.$tabs.eq(curIndex + 1);
					}
					this.switchTabs($tab, $newTab);
					e.stopPropagation();
					e.preventDefault();
					return false;
				}
			}
			return true;
		},
		// Panel click event handler for accordion only to highlight clicked paneld
		panelElementsClickHandler: function($panel, e) {
			var $tab = $('#'+$panel.attr('aria-labelledby')).attr('tabindex', '0').attr('aria-selected', 'true').addClass('selected');
			this.$tabs.not($tab).attr('tabindex', '-1').attr('aria-selected', 'false').removeClass('selected');
			return true;
		}
	};
	$.fn[pluginName] = function(options) {
		return this.each(function() {
			if(!$.data(this, 'plugin_'+pluginName)) {
				$.data(this, 'plugin_'+pluginName, new Plugin(this, options));
			}
		});
	};
})(jQuery, window, document);
<commit_msg>Update jquery.TabaKordion.js<commit_after>/*
* jQuery TabaKordion - v2.0.0
* A fully accessible to WAI specification; tabs and accordion jQuery plugin.
* http://www.tariqkhan.co.uk/
*
* Made by Tariq Khan
* Under MIT License
*/
(function($, window, undefined) {
	"use strict";
	var	pluginName = 'TabaKordion',
		defaults = {
			accordion:		true,
			openFirst:		true,
			multiSelect:	true,
			showHide:		false
		};
	function TabaKordion(element, options) {
		this.element = element;
		this.options = $.extend( {}, defaults, options);
		this._defaults = defaults;
		this._name = pluginName;
		this.$TabaKordion = $(element);
		if(this.$TabaKordion.hasClass('tabs')) {
			this.options.accordion = false;
		} else if(this.$TabaKordion.hasClass('nomultiselect')) {
			this.options.multiSelect = false;
		} else if(this.$TabaKordion.hasClass('showhide')) {
			this.options.showHide = true;
		}
		this.$tabs = this.options.showHide ? this.$TabaKordion : this.$TabaKordion.find('.tab');
		this.$panels = this.options.showHide ? $(this.$TabaKordion.attr('href')) : this.$TabaKordion.children('.panel');
		this.keys = new keyCodes();
		this.bindEventHandlers();
		this.init();
	}
	function keyCodes() {
		this.tab		= 9;
		this.enter		= 13;
		this.esc		= 27;
		this.space		= 32;
		this.pageup		= 33;
		this.pagedown	= 34;
		this.end		= 35;
		this.home		= 36;
		this.left		= 37;
		this.up			= 38;
		this.right		= 39;
		this.down		= 40;
	}
	$.extend($.expr[':'], { // focusable: jQuery ext. to add :focusable selector to get all focusable elements in a panel; credit to ajpiano on jQuery forums
		focusable: function(element) {
			var	nodeName = element.nodeName.toLowerCase(),
				tabIndex = $(element).attr('tabindex');
			if(($(element)[(nodeName == 'area' ? 'parents' : 'closest')](':hidden').length) == true) {
				return false;
			}
			if(!isNaN(tabIndex) && tabIndex < 0) {
				return false;
			}
			if(/input|select|textarea|button|object/.test(nodeName) == true) {
				return !element.disabled;
			}
			if((nodeName == 'a' ||  nodeName == 'area') == true) {
				return (element.href.length > 0);
			}
			return false;
		}
	});
	TabaKordion.prototype = {
		init: function() {
			if(this.options.showHide) {
				this.$tabs.attr('aria-expanded', 'false').attr('aria-controls', this.$panels.attr('id')).prepend('Show ');
				this.$panels.attr('aria-expanded', 'false').attr('aria-labelledby', this.$tabs.attr('id')).attr('role', 'region').attr('tabindex', '-1').addClass('showhide-region').hide();
			} else {
				this.$panels.attr('role', 'tabpanel').attr('aria-hidden', 'true').hide();
				this.$tabs.attr('role', 'tab').attr('aria-selected', 'false').attr('tabindex', '-1');
				if(this.options.accordion) {
					if(this.options.multiSelect) {
						this.$TabaKordion.attr('multiselectable', true);
					}
					this.$tabs.attr('aria-expanded', 'false').append(' (collapsed)');
				} else {
					this.$tabs.find('a').each(function() {
						$(this).replaceWith(''+$(this).text()+'');
					});
				}
			}
			var anchor = window.location.hash;
			if(anchor && $(anchor).length) {
				this.$tabs.removeClass('selected');
				$(anchor).addClass('selected');
				if(this.options.showHide) {
					anchor = anchor.substring(1);
					if(this.$tabs.attr('id') === anchor || this.$panels.attr('id') === anchor) {
						this.toggleRegion();
					}
				}
			}
			if(!this.options.showHide) {
				var $tab = this.$tabs.filter('.selected');
				if(!$tab.length && this.options.openFirst) {
					$tab = this.$tabs.first();
				}
				$tab.addClass('selected').attr('aria-selected', 'true').attr('tabindex', '0');
				if(this.options.accordion) {
					$tab.attr('aria-expanded', 'true').find('.visually-hidden').text('(expanded)');
				}
				this.$TabaKordion.find('#'+$tab.attr('aria-controls')).show().attr('aria-hidden', 'false');
			}
		},
		bindEventHandlers: function() {
			var thisObj = this;
			this.$tabs.keydown(function(e) {
				return thisObj.tabKeyDownHandler($(this), e);
			});
			this.$tabs.click(function(e) {
				return thisObj.tabClickHandler($(this), e);
			});
			this.$tabs.focus(function(e) {
				return thisObj.tabFocusHandler($(this), e);
			});
			this.$tabs.blur(function(e) {
				return thisObj.tabBlurHandler($(this), e);
			});
			if(!this.options.showHide) {
				this.$panels.keydown(function(e) {
					return thisObj.panelElementsKeyDownHandler($(this), e);
				});
			}
			if(this.options.accordion) {
				this.$panels.click(function(e) {
					return thisObj.panelElementsClickHandler($(this), e);
				});
			}
			if(this.options.showHide) {
				this.$panels.blur(function(e) {
					return thisObj.panelBlurHandler($(this), e);
				});
			}
		},
		toggleRegion: function() {
			var $tab = this.$tabs;
			this.$panels.slideToggle(function() {
				if($(this).attr('aria-expanded') === 'false') {
					$(this).attr('aria-expanded', 'true').focus();
					$tab.attr('aria-expanded', 'true').addClass('selected').find('span').html('Hide');
				} else {
					$(this).attr('aria-expanded', 'false');
					$tab.attr('aria-expanded', 'false').removeClass('selected').find('span').html('Show');
				}
			});
		},
		togglePanel: function($tab) {
			if(!this.options.accordion) {
				this.$panels.attr('aria-hidden', 'true').hide();
			} else if(this.options.accordion && !this.options.multiSelect) {
				this.$tabs.attr('aria-expanded', 'false').find('.visually-hidden').text('(collapsed)');
				this.$panels.attr('aria-hidden', 'true').slideUp();
			}
			var $panel = this.$TabaKordion.find('#'+$tab.attr('aria-controls'));
			if($panel.attr('aria-hidden') === 'true') {
				$panel.attr('aria-hidden', 'false');
				if(this.options.accordion) {
					$tab.attr('aria-expanded', 'true').find('.visually-hidden').text('(expanded)');
					$panel.slideDown();
				} else {
					$panel.show();
				}
			} else {
				$panel.attr('aria-hidden', 'true');
				if(this.options.accordion) {
					$tab.attr('aria-expanded', 'false').find('.visually-hidden').text('(collapsed)');
					$panel.slideUp();
				} else {
					$panel.hide();
				}
			}
		},
		switchTabs: function($curTab, $newTab) {
			$curTab.removeClass('selected focus').attr('tabindex', '-1').attr('aria-selected', 'false');
			if(!this.options.accordion) {
				this.$TabaKordion.find('#'+$curTab.attr('aria-controls')).hide().attr('aria-hidden', 'true');
				this.$TabaKordion.find('#'+$newTab.attr('aria-controls')).show().attr('aria-hidden', 'false');
			}
			$newTab.addClass('selected').attr('aria-selected', 'true').attr('tabindex', '0').focus();
		},
		tabKeyDownHandler: function($tab, e) {
			if(e.altKey || (this.options.showHide && (e.keyCode !== this.keys.enter && e.keyCode !== this.keys.space))) {
				return true;
			}
			switch(e.keyCode) {
				case this.keys.enter:
				case this.keys.space: {
					if(this.options.accordion) {
						this.options.showHide ? this.toggleRegion() : this.togglePanel($tab);
						e.stopPropagation();
						return false;
					}
					return true;
				}
				case this.keys.left:
				case this.keys.up: {
					if(e.ctrlKey) {
					} else {
						var	tabIndex = this.$tabs.index($tab),
							$newTab = tabIndex == 0 ? this.$tabs.last() : this.$tabs.eq(tabIndex - 1);
						this.switchTabs($tab, $newTab);
					}
					e.stopPropagation();
					return false;
				}
				case this.keys.right:
				case this.keys.down: {
					var	tabIndex = this.$tabs.index($tab),
						$newTab = tabIndex == this.$tabs.length - 1 ? this.$tabs.first() : this.$tabs.eq(tabIndex + 1);
					this.switchTabs($tab, $newTab);
					e.stopPropagation();
					return false;
				}
				case this.keys.home: {
					this.switchTabs($tab, this.$tabs.first());
					e.stopPropagation();
					return false;
				}
				case this.keys.end: {
					this.switchTabs($tab, this.$tabs.last());
					e.stopPropagation();
					return false;
				}
			}
		},
		tabClickHandler: function($tab, e) {
			if(this.options.showHide) {
				this.toggleRegion();
			} else {
				$tab.addClass('selected').attr('tabindex', '0').attr('aria-selected', 'true');
				this.$tabs.not($tab).attr('tabindex', '-1').attr('aria-selected', 'false').removeClass('selected');
				this.togglePanel($tab);
			}
			if(this.options.accordion) {
				e.stopPropagation();
				return false;
			}
			return true;
		},
		tabFocusHandler: function($tab, e) {
			$tab.addClass('focus');
			return true;
		},
		tabBlurHandler: function($tab, e) {
			$tab.removeClass('focus');
			return true;
		},
		panelElementsKeyDownHandler: function($panel, e) {
			if(e.altKey) {
				return true;
			}
			switch(e.keyCode) {
				case this.keys.tab: {
					var	$focusable	= $panel.find(':focusable'),
						curIndex	= $focusable.index($(e.target)),
						panelIndex	= this.$panels.index($panel),
						numPanels	= this.$panels.length;
					if(e.shiftKey) {
						if(curIndex == 0 && panelIndex > 0) {
							for(var index = panelIndex - 1; index >= 0; index--) {
								var	$prevPanel = this.$panels.eq(index),
									$prevTab = $('#' + $prevPanel.attr('aria-labelledby'));
								$focusable.length = 0;
								$focusable = $prevPanel.find(':focusable');
									if($focusable.length > 0) {
									$focusable.last().focus();
									this.$tabs.attr('aria-selected', 'false').removeClass('selected');
									$prevTab.attr('aria-selected', 'true').addClass('selected');
									e.stopPropagation;
									return false;
								}
							}
						}
					} else if (panelIndex < numPanels) {
						if(curIndex == $focusable.length - 1) {
							for(var index = panelIndex + 1; index < numPanels; index++) {
								var	$nextPanel = this.$panels.eq(index),
									$nextTab = $('#'+$nextPanel.attr('aria-labelledby'));
								$focusable.length = 0;
								$focusable = $nextPanel.find(':focusable');
								if($focusable.length > 0) {
									$focusable.first().focus();
									this.$tabs.attr('aria-selected', 'false').removeClass('selected');
									$nextTab.attr('aria-selected', 'true').addClass('selected');
									e.stopPropagation;
									return false;
								}
							}
						}
					}
					break;
				}
				case this.keys.esc:
				case this.keys.left:
				case this.keys.up: {
					if(e.keyCode != this.keys.esc && !e.ctrlKey) {
						return true;
					}
					$('#'+$panel.attr('aria-labelledby')).focus();
					e.stopPropagation();
					return false;
				}
				case this.keys.pageup:
				case this.keys.pagedown: {
					if(!e.ctrlKey) {
						return true;
					}
					var	$tab = this.$tabs.filter('.selected'),
						curIndex = this.$tabs.index($tab);
					if(e.keyCode == this.keys.pageup) {
						var	$newTab = curIndex == 0 ? this.$tabs.last() : this.$tabs.eq(curIndex - 1);
					} else {
						var	$newTab = curIndex == this.$tabs.length - 1 ? this.$tabs.first() : this.$tabs.eq(curIndex + 1);
					}
					this.switchTabs($tab, $newTab);
					e.stopPropagation();
					e.preventDefault();
					return false;
				}
			}
			return true;
		},
		panelBlurHandler: function($panel, e) {
			this.$tabs.removeClass('selected');
			return true;
		},
		panelElementsClickHandler: function($panel, e) {
			if(this.options.showHide) {
				this.$tabs.addClass('selected');
			} else {
				var $tab = $('#'+$panel.attr('aria-labelledby')).attr('tabindex', '0').attr('aria-selected', 'true').addClass('selected');
				this.$tabs.not($tab).attr('tabindex', '-1').attr('aria-selected', 'false').removeClass('selected');
			}
			return true;
		}
	};
	$.fn[pluginName] = function(options) {
		return this.each(function() {
			if(!$.data(this, 'plugin_'+pluginName)) {
				$.data(this, 'plugin_'+pluginName, new TabaKordion(this, options));
			}
		});
	}
})(jQuery, window);
<|endoftext|>


<commit_before>/**
 * NCT URL parser module
 */
/*global getSongInfoNct:true*/

// Utility / Private functions
var nctRegExp = /\/([^\/]+)\.([^.]+)\.html/;

/**
 * [parseNctURL description]
 * @param  {[type]} songurl [description]
 * @return {[type]}         [description]
 */
function parseNctURL(songurl) {
	var parsed = String(songurl).match(nctRegExp);

	return {
		name: toTitleCase(parsed[1]).split('-').join(' '),
		id: String(parsed[2])
	};
}

/**
 * [toTitleCase description]
 * @param  {[type]} s [description]
 * @return {[type]}   [description]
 */
function toTitleCase(s) {
	s = String(s).toLowerCase();
	return s.replace( /\b[a-z]/g, function(f) { return f.toUpperCase(); } );
}

/**
 * Get NCT stream URL and other info
 *
 * @param  {[type]} songurl [description]
 * @return {[type]}         [description]
 */
getSongInfoNct = function(songurl) {
	var res;
	var songInfo = parseNctURL(songurl);
	console.log(songInfo.name, ' - ', songInfo.id);

	try {
		// old way
		// res = HTTP.get('http://www.nhaccuatui.com/download/song/' + songInfo.id);

		res = HTTP.post('http://getlinkmusic.appspot.com/getlinkjson', {
			params: {
				tb_link: songurl
			}
		});

		//TODO: Use the getSongInfoZing approach to parse the URL internally and not rely on 3rd party service

		console.log('Response:', res);
		res = JSON.parse(res.content); // ignore headers and status code
	} catch (err) {
		console.error('Get NCT stream Error', err);
	}
	// sample response:
	// {
	//   "data": [
	//     {
	//       "creator": "Maroon 5",
	//       "lyric": "http://lrc.nct.nixcdn.com/2014/08/19/f/0/c/0/1408465315444.lrc",
	//       "location": "http://aredir.nixcdn.com/3c9ac28f32f504cba923fe8e0086f94e/5406968d/Unv_Audio20/Maps-Maroon5-3298999.mp3",
	//       "title": "Maps"
	//     }
	//   ]
	// }

	res = res.data[0];
	console.log(res);
	if (res && res.location) {
		console.log('Adding new Song');

		return {
			timeAdded: Date.now(),
			originalURL: songurl,
			origin: 'NCT',
			name: res.title,
			artist: res.creator,
			streamURL: res.location
		};
	} else {
		return {
			error: 'Can\'t parse and get song info from link'
		};
	}
};<commit_msg>Fix a potential error when getting NCT song info<commit_after>/**
 * NCT URL parser module
 */
/*global getSongInfoNct:true*/

// Utility / Private functions
var nctRegExp = /\/([^\/]+)\.([^.]+)\.html/;

/**
 * [parseNctURL description]
 * @param  {[type]} songurl [description]
 * @return {[type]}         [description]
 */
function parseNctURL(songurl) {
	var parsed = String(songurl).match(nctRegExp);

	return {
		name: toTitleCase(parsed[1]).split('-').join(' '),
		id: String(parsed[2])
	};
}

/**
 * [toTitleCase description]
 * @param  {[type]} s [description]
 * @return {[type]}   [description]
 */
function toTitleCase(s) {
	s = String(s).toLowerCase();
	return s.replace( /\b[a-z]/g, function(f) { return f.toUpperCase(); } );
}

/**
 * Get NCT stream URL and other info
 *
 * @param  {[type]} songurl [description]
 * @return {[type]}         [description]
 */
getSongInfoNct = function(songurl) {
	var res;
	var songInfo = parseNctURL(songurl);
	console.log(songInfo.name, ' - ', songInfo.id);

	try {
		// old way
		// res = HTTP.get('http://www.nhaccuatui.com/download/song/' + songInfo.id);

		res = HTTP.post('http://getlinkmusic.appspot.com/getlinkjson', {
			params: {
				tb_link: songurl
			}
		});

		//TODO: Use the getSongInfoZing approach to parse the URL internally and not rely on 3rd party service

		console.log('Response:', res);
		res = JSON.parse(res.content); // ignore headers and status code
		res = res.data[0];
	} catch (err) {
		console.error('Get NCT stream Error', err);
	}
	// sample response:
	// {
	//   "data": [
	//     {
	//       "creator": "Maroon 5",
	//       "lyric": "http://lrc.nct.nixcdn.com/2014/08/19/f/0/c/0/1408465315444.lrc",
	//       "location": "http://aredir.nixcdn.com/3c9ac28f32f504cba923fe8e0086f94e/5406968d/Unv_Audio20/Maps-Maroon5-3298999.mp3",
	//       "title": "Maps"
	//     }
	//   ]
	// }

	console.log(res);
	if (res && res.location) {
		console.log('Adding new Song');

		return {
			timeAdded: Date.now(),
			originalURL: songurl,
			origin: 'NCT',
			name: res.title,
			artist: res.creator,
			streamURL: res.location
		};
	} else {
		return {
			error: 'Can\'t parse and get song info from link'
		};
	}
};
<|endoftext|>


<commit_before>var Player = function (name, position) {
  this.alive = true;
  this.name = name;
  this.position = position;
  this.image = new Image();
  this.image.src = "/mariosub.png";

  this.ghostImage = new Image();
  this.ghostImage.src = "/ghost.png";
  // this.image.onload = this.draw;

  this.color1 = 0;
};

Player.prototype.draw = function () {
  if(this.alive){
    context.drawImage(this.image, this.position.x * TILESIZE, this.position.y * TILESIZE, TILESIZE, TILESIZE);
    // context.drawImage(this.image, canvas.width/2, canvas.height/2, TILESIZE, TILESIZE);
  }else{
    context.drawImage(this.ghostImage, this.position.x * TILESIZE, this.position.y * TILESIZE, TILESIZE, TILESIZE);
  }
};

Player.prototype.update = function (options) {
  if(options.keys.length){
    for(var i = 0; i < options.keys.length; i++){
      if(options.keys[i] == 'left'){
        this.direction = 'left';
        if(this.canMoveTo(this.position.x - 1, this.position.y)){
          this.position.x -= 1;
        }
      }
      if(options.keys[i] == 'right'){
        this.direction = 'right';
        if(this.canMoveTo(this.position.x + 1, this.position.y)){
          this.position.x += 1;
        }
      }
      if(options.keys[i] == 'up'){
        this.direction = 'up';
        if(this.canMoveTo(this.position.x, this.position.y - 1)){
          this.position.y -= 1;
        }
      }
      if(options.keys[i] == 'down'){
        this.direction = 'down';
        if(this.canMoveTo(this.position.x, this.position.y + 1)){
          this.position.y += 1;
        }
      }

      if(options.keys[i] == 'leavemine'){
        minesCollection.newMine(this.position);
      }
    }
  }

  // player is caught in the explosion
  if(board.tiles[this.position.x][this.position.y].exploding){
    this.alive = false;
  }
};

Player.prototype.canMoveTo = function(x, y){
  if(! board.exists(x,y)){ return false; }

  var cannotAccess = ['wall', 'hardwall'];
  for(var i = 0; i < cannotAccess.length; i++){
    if(board.tiles[x][y].type == cannotAccess[i]){
      return false;
    }
  }

  // TODO: come up with a better name for this
  var blockingTypes = [Mine];
  for(var j = 0; j < board.tiles[x][y].items.length; j++){
    for(var i = 0; i < blockingTypes.length; i++){
      if(board.tiles[x][y].items[j].constructor === blockingTypes[i]){
       return false;
      }
    }
  }

  return true;
};

Player.prototype.drawFrame1 = function () {
  context.beginPath();
  context.fillStyle = '#888';
  context.moveTo(this.position.x * TILESIZE, this.position.y * TILESIZE);
  context.lineTo(this.position.x * TILESIZE + TILESIZE, this.position.y * TILESIZE);
  context.lineTo(this.position.x * TILESIZE + TILESIZE, this.position.y * TILESIZE + TILESIZE);
  context.lineTo(this.position.x * TILESIZE, this.position.y * TILESIZE + TILESIZE);
  context.closePath();
  context.fill();
};
<commit_msg>drawing player with canvas<commit_after>var Player = function (name, position) {
  this.alive = true;
  this.name = name;
  this.position = position;
  this.direction = 'up';

  this.ghostImage = new Image();
  this.ghostImage.src = "/ghost.png";
  // this.image.onload = this.draw;
};

Player.prototype.draw = function () {
  if(this.alive){
    //context.drawImage(this.image, this.position.x * TILESIZE, this.position.y * TILESIZE, TILESIZE, TILESIZE);
    // context.drawImage(this.image, canvas.width/2, canvas.height/2, TILESIZE, TILESIZE);

    if(this.direction == 'up') this.drawUp();
    if(this.direction == 'down') this.drawDown();
    if(this.direction == 'left') this.drawLeft();
    if(this.direction == 'right') this.drawRight();
  }else{
    context.drawImage(this.ghostImage, this.position.x * TILESIZE, this.position.y * TILESIZE, TILESIZE, TILESIZE);
  }
};

Player.prototype.update = function (options) {
  if(options.keys.length){
    for(var i = 0; i < options.keys.length; i++){
      if(options.keys[i] == 'left'){
        this.direction = 'left';
        if(this.canMoveTo(this.position.x - 1, this.position.y)){
          this.position.x -= 1;
        }
      }
      if(options.keys[i] == 'right'){
        this.direction = 'right';
        if(this.canMoveTo(this.position.x + 1, this.position.y)){
          this.position.x += 1;
        }
      }
      if(options.keys[i] == 'up'){
        this.direction = 'up';
        if(this.canMoveTo(this.position.x, this.position.y - 1)){
          this.position.y -= 1;
        }
      }
      if(options.keys[i] == 'down'){
        this.direction = 'down';
        if(this.canMoveTo(this.position.x, this.position.y + 1)){
          this.position.y += 1;
        }
      }

      if(options.keys[i] == 'leavemine'){
        minesCollection.newMine(this.position);
      }
    }
  }

  // player is caught in the explosion
  if(board.tiles[this.position.x][this.position.y].exploding){
    this.alive = false;
  }
};

Player.prototype.canMoveTo = function(x, y){
  if(! board.exists(x,y)){ return false; }

  var cannotAccess = ['wall', 'hardwall'];
  for(var i = 0; i < cannotAccess.length; i++){
    if(board.tiles[x][y].type == cannotAccess[i]){
      return false;
    }
  }

  // TODO: come up with a better name for this
  var blockingTypes = [Mine];
  for(var j = 0; j < board.tiles[x][y].items.length; j++){
    for(var i = 0; i < blockingTypes.length; i++){
      if(board.tiles[x][y].items[j].constructor === blockingTypes[i]){
       return false;
      }
    }
  }

  return true;
};

Player.prototype.drawUp = function () {
  context.beginPath();
  context.fillStyle = 'green';
  context.moveTo(this.position.x * TILESIZE + (TILESIZE/2), this.position.y * TILESIZE);
  context.lineTo(this.position.x * TILESIZE + TILESIZE - (TILESIZE/2), this.position.y * TILESIZE);
  context.lineTo(this.position.x * TILESIZE + TILESIZE, this.position.y * TILESIZE + TILESIZE);
  context.lineTo(this.position.x * TILESIZE, this.position.y * TILESIZE + TILESIZE);
  context.closePath();
  context.fill();
};

Player.prototype.drawDown = function () {
  context.beginPath();
  context.fillStyle = 'green';
  context.moveTo(this.position.x * TILESIZE, this.position.y * TILESIZE);
  context.lineTo(this.position.x * TILESIZE + TILESIZE, this.position.y * TILESIZE);
  context.lineTo(this.position.x * TILESIZE + (TILESIZE/2), this.position.y * TILESIZE + TILESIZE);
  context.lineTo(this.position.x * TILESIZE + (TILESIZE/2), this.position.y * TILESIZE + TILESIZE);
  context.closePath();
  context.fill();
};

Player.prototype.drawLeft = function () {
  context.beginPath();
  context.fillStyle = 'green';
  // take the pen and move to top left corner of tile
  context.moveTo(this.position.x * TILESIZE,
                 this.position.y * TILESIZE + (TILESIZE/2));

  // take the pen and move to the top right corner
  context.lineTo(this.position.x * TILESIZE + TILESIZE,
                 this.position.y * TILESIZE);

  // move to the opposite corner
  context.lineTo(this.position.x * TILESIZE + TILESIZE,
                 this.position.y * TILESIZE + TILESIZE);

  // move to 'bottom left'
  context.lineTo(this.position.x * TILESIZE,
                 this.position.y * TILESIZE + (TILESIZE/2));

  context.closePath();
  context.fill();
};

Player.prototype.drawRight = function () {
  context.beginPath();
  context.fillStyle = 'green';
  // take the pen and move to top left corner of tile
  context.moveTo(this.position.x * TILESIZE,
                 this.position.y * TILESIZE);

  // take the pen and move to the top right corner
  context.lineTo(this.position.x * TILESIZE + TILESIZE,
                 this.position.y * TILESIZE + (TILESIZE/2));

  // move to the opposite corner
  context.lineTo(this.position.x * TILESIZE + TILESIZE,
                 this.position.y * TILESIZE + (TILESIZE/2));

  // move to 'bottom left'
  context.lineTo(this.position.x * TILESIZE,
                 this.position.y * TILESIZE + TILESIZE);

  context.closePath();
  context.fill();
};
<|endoftext|>


<commit_before>// we use FS to read the pakage.json and to display a pretty banner
var FS = require("fs");

// bahn is global variable that contains
//  - package info (including config): bahn.package / bahn.package.config
//  - access to database: bahn.databse
//  - access to the http service: bahn.http
//  - access to the socket service: bahn.socket
global.bahn = {};

// load up package.json
bahn.package = require("./package");
console.info("bahn configuration file loaded. Initialising services:\n");

// show the pretty banner and version info
var banner = FS.readFileSync("./banner.txt");
console.info(banner.toString());
console.info("Version: " + bahn.package.version + "\n");

// fire up the database services...
if (bahn.package.config.database) {
    // Database uses NEDB, which promises to be transparant with Mongo
    // https://github.com/louischatriot/nedb
    Database = require("./application/database.js");
    bahn.database = new Database();
    console.log(" - NEDB database module is ready.");
}

// ...crank up the HTTP service...
var HTTP = require("./application/http.js");
bahn.http = new HTTP(bahn.package.config.port);
console.info(" - ExpressJS is listening at http://127.0.0.1:" + bahn.package.config.port);

// ...open all the sockets...
if (bahn.package.config.sockets) {
    var Socket = require("./application/socket.js");
    bahn.socket = new Socket(bahn.http.server, bahn.package.config.port);
    console.info(" - Socket.io is listening at http://127.0.0.1:" + bahn.package.config.port);
}

// ...as we got road!
console.info("\nReady.\n");
<commit_msg>mention MongoDB on start up<commit_after>// we use FS to read the pakage.json and to display a pretty banner
var FS = require("fs");

// bahn is global variable that contains
//  - package info (including config): bahn.package / bahn.package.config
//  - access to database: bahn.databse
//  - access to the http service: bahn.http
//  - access to the socket service: bahn.socket
global.bahn = {};

// load up package.json
bahn.package = require("./package");
console.info("bahn configuration file loaded. Initialising services:\n");

// show the pretty banner and version info
var banner = FS.readFileSync("./banner.txt");
console.info(banner.toString());
console.info("Version: " + bahn.package.version + "\n");

// fire up the database services...
if (bahn.package.config.database) {
    // Database uses NEDB, which promises to be transparant with MongoDB
    // https://github.com/louischatriot/nedb
    Database = require("./application/database.js");
    bahn.database = new Database();
    console.log(" - NeDB/MongoDB database module is ready.");
}

// ...crank up the HTTP service...
var HTTP = require("./application/http.js");
bahn.http = new HTTP(bahn.package.config.port);
console.info(" - ExpressJS is listening at http://127.0.0.1:" + bahn.package.config.port);

// ...open all the sockets...
if (bahn.package.config.sockets) {
    var Socket = require("./application/socket.js");
    bahn.socket = new Socket(bahn.http.server, bahn.package.config.port);
    console.info(" - Socket.io is listening at http://127.0.0.1:" + bahn.package.config.port);
}

// ...as we got road!
console.info("\nReady.\n");
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var props = require('props');
var dive = require('dive');
var ejs = require('ejs');
var async = require('async');
var clone = require('clone');
var append = require('append');

// Main function
var bake = function(conf, hooks, cb) {

  // File counter
  var todo = 0;

  if (typeof conf != 'object')
    return cb(new Error('parameter conf must be a valid configuration object'));

  // Set values for inputDir, outputDir and tplDir
  var root = conf.root || process.cwd();
  var inputDir = conf.directories.input || 'pub';
  var outputDir = conf.directories.output || 'pub';
  var tplDir = conf.directories.templates || 'tpl';

  inputDir = path.resolve(root, inputDir);
  outputDir = path.resolve(root, outputDir);
  tplDir = path.resolve(root, tplDir);

  // Set values for fileExt
  var fileExt = conf.fileExtensions || { txt: 'html' };
  var fileExtPattern
      = new RegExp('\\.(' + Object.keys(fileExt).join('|') + ')$', 'i');

  var ignore = conf.ignore || [];

  // Status log
  console.log('Beginning to bake ' + inputDir + '.');

  // Dive into the public directory
  dive(inputDir, function(err, master) {
    // Throw errors
    if (err)
      return cb(err);

    // Matching variable
    var match;
    var pathFromRoot = master.replace(inputDir, '').substr(1);

    // Match the master-file's name against enabled file extensions
    if ((match = master.match(fileExtPattern))
        && ignore.indexOf(pathFromRoot) == -1) {

      // Get the file extension of the master file
      var masterExt = match[1];

      // Increase file counter
      ++todo;

      // Read the master-file's contents
      fs.readFile(master, 'utf8', function(err, data) {
        // Throw errors
        if (err)
          return cb(err);

        // Get the properties
        // prop is the file specific property object
        var prop = props(data);

        // Amend prop by properties in conf.properties if defined
        if (conf.properties)
          prop = append(clone(conf.properties), prop);

        // Assert that prop.template is set
        if (typeof prop.template == 'undefined')
          prop.template = 'default.tpl';

        // __propBefore hook
        if (hooks.__propBefore)
          hooks.__propBefore(master, prop, propBeforeCB);
        else
          propBeforeCB(null, prop);

        function propBeforeCB(err, prop) {
          if (err)
            return cb(err);

          // tasks for async
          var tasks = {};

          // Various property hooks
          for (var key in prop) {(function (prop, key) {
            if (hooks[key])
              tasks[key] = function (callback) {
                return hooks[key](master, prop, callback);
              };
            else
              tasks[key] = function (callback) {
                return callback(null, prop[key]);
              };
          })(prop, key);}

          // run tasks in parallel
          async.parallel(tasks, function (err, prop) {
            if (err)
              return cb(err);

            // __propAfter hook
            if (hooks.__propAfter)
              hooks.__propAfter(master, prop, propAfterCB);
            else
              propAfterCB(null, prop);

            function propAfterCB(err, prop) {
              if (err)
                return cb(err);

              // Read the template file
              fs.readFile(path.resolve(tplDir, prop.template), 'utf8',
                  function(err, result) {
                // Throw errors
                if (err)
                  return cb(err);

                // (Pre-)Insert the content (so ejs-tags in
                // prop.__content are parsed, too.
                result = result.replace(/<%=\s+__content\s+%>/g,
                    prop.__content);

                // Result's filename
                var resName = master.replace(fileExtPattern,
                    '.' + fileExt[masterExt]);

                // New file's path
                if (typeof prop._id == 'undefined') {
                  prop._id = resName.replace(inputDir, '');
                  prop._id = prop._id.replace(new RegExp('\\\\', 'g'), '/');
                }

                // Render ejs-template
                result = ejs.render(result, { locals: prop });

                // Remove first slash
                if (/^\//.test(prop._id))
                  prop._id = prop._id.substring(1);

                // absolute path
                resName = path.resolve(outputDir, prop._id);

                // Write contents
                fs.writeFile(resName, result, function(err) {
                  // Throw errors
                  if (err)
                    return cb(err);

                  // __writeAfter hook
                  if (hooks.__writeAfter)
                    hooks.__writeAfter(master, prop, writeAfterCB);
                  else
                    writeAfterCB(null);

                  function writeAfterCB(err) {
                    if (err)
                      return cb(err);

                    // Log status on success
                    console.log('  * ' + resName + ' written.');

                    // When file counter is zero
                    if (!--todo) {
                      // __complete hook
                      if (hooks.__complete)
                        hooks.__complete(master, prop, cb);
                      else
                        return cb(null);
                    }
                  }
                });
              });
            }
          });
        }
      });
    }
  });
};

module.exports = bake;
<commit_msg>Ensure there is final callback<commit_after>var fs = require('fs');
var path = require('path');
var props = require('props');
var dive = require('dive');
var ejs = require('ejs');
var async = require('async');
var clone = require('clone');
var append = require('append');

// Main function
var bake = function(conf, hooks, cb) {

  // File counter
  var todo = 0;
  var matched = 0;

  if (typeof conf != 'object')
    return cb(new Error('parameter conf must be a valid configuration object'));

  // Set values for inputDir, outputDir and tplDir
  var root = conf.root || process.cwd();
  var inputDir = conf.directories.input || 'pub';
  var outputDir = conf.directories.output || 'pub';
  var tplDir = conf.directories.templates || 'tpl';

  inputDir = path.resolve(root, inputDir);
  outputDir = path.resolve(root, outputDir);
  tplDir = path.resolve(root, tplDir);

  // Set values for fileExt
  var fileExt = conf.fileExtensions || { txt: 'html' };
  var fileExtPattern
      = new RegExp('\\.(' + Object.keys(fileExt).join('|') + ')$', 'i');

  var ignore = conf.ignore || [];

  // Status log
  console.log('Beginning to bake ' + inputDir + '.');

  // Dive into the public directory
  dive(inputDir, function(err, master) {
    // Throw errors
    if (err)
      return cb(err);

    // Matching variable
    var match;
    var pathFromRoot = master.replace(inputDir, '').substr(1);

    // Match the master-file's name against enabled file extensions
    if ((match = master.match(fileExtPattern))
        && ignore.indexOf(pathFromRoot) == -1) {

      // Get the file extension of the master file
      var masterExt = match[1];

      // Increase file counter
      ++todo;
      ++matched;

      // Read the master-file's contents
      fs.readFile(master, 'utf8', function(err, data) {
        // Throw errors
        if (err)
          return cb(err);

        // Get the properties
        // prop is the file specific property object
        var prop = props(data);

        // Amend prop by properties in conf.properties if defined
        if (conf.properties)
          prop = append(clone(conf.properties), prop);

        // Assert that prop.template is set
        if (typeof prop.template == 'undefined')
          prop.template = 'default.tpl';

        // __propBefore hook
        if (hooks.__propBefore)
          hooks.__propBefore(master, prop, propBeforeCB);
        else
          propBeforeCB(null, prop);

        function propBeforeCB(err, prop) {
          if (err)
            return cb(err);

          // tasks for async
          var tasks = {};

          // Various property hooks
          for (var key in prop) {(function (prop, key) {
            if (hooks[key])
              tasks[key] = function (callback) {
                return hooks[key](master, prop, callback);
              };
            else
              tasks[key] = function (callback) {
                return callback(null, prop[key]);
              };
          })(prop, key);}

          // run tasks in parallel
          async.parallel(tasks, function (err, prop) {
            if (err)
              return cb(err);

            // __propAfter hook
            if (hooks.__propAfter)
              hooks.__propAfter(master, prop, propAfterCB);
            else
              propAfterCB(null, prop);

            function propAfterCB(err, prop) {
              if (err)
                return cb(err);

              // Read the template file
              fs.readFile(path.resolve(tplDir, prop.template), 'utf8',
                  function(err, result) {
                // Throw errors
                if (err)
                  return cb(err);

                // (Pre-)Insert the content (so ejs-tags in
                // prop.__content are parsed, too.
                result = result.replace(/<%=\s+__content\s+%>/g,
                    prop.__content);

                // Result's filename
                var resName = master.replace(fileExtPattern,
                    '.' + fileExt[masterExt]);

                // New file's path
                if (typeof prop._id == 'undefined') {
                  prop._id = resName.replace(inputDir, '');
                  prop._id = prop._id.replace(new RegExp('\\\\', 'g'), '/');
                }

                // Render ejs-template
                result = ejs.render(result, { locals: prop });

                // Remove first slash
                if (/^\//.test(prop._id))
                  prop._id = prop._id.substring(1);

                // absolute path
                resName = path.resolve(outputDir, prop._id);

                // Write contents
                fs.writeFile(resName, result, function(err) {
                  // Throw errors
                  if (err)
                    return cb(err);

                  // __writeAfter hook
                  if (hooks.__writeAfter)
                    hooks.__writeAfter(master, prop, writeAfterCB);
                  else
                    writeAfterCB(null);

                  function writeAfterCB(err) {
                    if (err)
                      return cb(err);

                    // Log status on success
                    console.log('  * ' + resName + ' written.');

                    // When file counter is zero
                    if (!--todo) {
                      // __complete hook
                      if (hooks.__complete)
                        hooks.__complete(master, prop, cb);
                      else
                        return cb(null);
                    }
                  }
                });
              });
            }
          });
        }
      });
    }
  }, function () {
    if (todo == 0)
      return cb();
  });
};

module.exports = bake;
<|endoftext|>


<commit_before>/**
 * Created by dani on 06.01.16.
 */

'use strict';

var _ = require('lodash');
var Promise = require('bluebird');
var crypto = require('crypto');
var Model = require('../helpers/Model');

var fields = {
  plot: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_sector'}
    }
  },
  visit: {
    type: 'choice',
    required: true,
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_visit_number'}
    }
  },
  secondaryHabitat: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_habitat'}
    }
  },
  primaryHabitat: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_habitat'}
    }
  },
  distance: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_distance'}
    }
  },
  species: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'species',
      filter: {type: 'birds'}
    }
  },
  cloudiness: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_cloud_level'}
    }
  },
  windDirection: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_wind_direction'}
    }
  },
  windSpeed: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_wind_force'}
    }
  },
  rain: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_rain'}
    }
  },

  count: {
    type: '+int',
    required: true
  },
  endDateTime: {
    type: 'timestamp',
    required: true
  },
  startDateTime: {
    type: 'timestamp',
    required: true
  },
  notes: 'text',
  visibility: '+num',
  mto: 'text',
  cloudsType: 'text',
  temperature: 'num',
  observers: 'text',
  latitude: {
    type: 'num',
    required: true,
    uniqueHash: true,
  },
  longitude: {
    type: 'num',
    required: true,
    uniqueHash: true,
  },

  observationDateTime: {
    type: 'timestamp',
    required: true,
    uniqueHash: true,
  },
  monitoringCode: {
    type: 'text',
    required: true
  },

  zone: {
    type: 'choice',
    required: true,
    relation: {
      model: 'zone'
    }
  },

  threats: {
    type: 'multi',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_threats'}
    }
  },

  user: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    observationDateTime: '2015-12-10T12:15:04Z',
    relation: {
      model: 'user'
    }
  },

  pictures: 'json',
  track: 'text',
};

module.exports = function (sequelize, DataTypes) {
  var fieldsDef = {
    hash: DataTypes.STRING(64)
  };

  _.forEach(fields, function (field, name) {
    if (_.isString(field)) {
      field = {type: field};
    }

    var fd = {
      allowNull: !field.required
    };

    switch (field.type) {
      case 'multi':
      case 'choice':
      {
        switch (field.relation.model) {
          case 'nomenclature':
          {
            fieldsDef[name + 'Bg'] = _.extend({
              type: DataTypes.TEXT
            }, fd);
            fieldsDef[name + 'En'] = _.extend({
              type: DataTypes.TEXT
            }, fd);
            break;
          }
          case 'species':
          {
            fieldsDef[name] = _.extend({
              type: DataTypes.TEXT
            }, fd);
            break;
          }
          case 'zone':
          {
            fieldsDef[name + 'Id'] = _.extend({
              type: field.type === 'multi'
                ? DataTypes.TEXT
                : DataTypes.STRING(10)
            }, fd);
            break;
          }
          case 'user':
          {
            fieldsDef[name + 'Id'] = _.extend({
              type: field.type === 'multi'
                ? DataTypes.TEXT
                : DataTypes.INTEGER
            });
            break;
          }
          default:
            throw new Error('[' + name + '] Unknown relation model ' + field.relation.model);
        }
        break;
      }
      case 'timestamp':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.DATE
        }, fd);
        break;
      }
      case 'float':
      case '+num':
      case 'num':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.FLOAT
        }, fd);
        break;
      }
      case '+int':
      case 'int':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.INTEGER
        }, fd);
        break;
      }
      case 'text':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.TEXT
        }, fd);
        break;
      }
      case 'json':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.TEXT
        }, fd);
        break;
      }
      default:
        throw new Error('[' + name + '] Unknown field type ' + field.type);

    }
  });

  var model = sequelize.define('FormCBM', fieldsDef, {
    freezeTableName: true,
    indexes: [
      {fields: ['species']},
      {fields: ['zoneId']},
      {fields: ['userId']}
    ],
    classMethods: {
      associate: function (models) {
        models.formCBM.belongsTo(models.species, {as: 'speciesInfo', foreignKey: 'species', targetKey: 'labelLa'});
        models.formCBM.belongsTo(models.zone, {as: 'zone'});
        models.formCBM.belongsTo(models.user, {as: 'user'});
      }
    },
    instanceMethods: {
      calculateHash: Model.generateCalcHash(fields),
      apiData: function (api) {
        var data = {};
        var self = this;
        return Promise.props(_.mapValues(fields, function (field, name) {
          if (_.isString(field)) field = {type: field};
          switch (field.type) {
            case 'multi':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  var res = [];
                  var bg = self[name + 'Bg'] && self[name + 'Bg'].split('|').map(function (val) {
                      return val.trim();
                    }) || [];
                  var en = self[name + 'En'] && self[name + 'En'].split('|').map(function (val) {
                      return val.trim();
                    }) || [];
                  while (bg.length && en.length) {
                    res.push({
                      label: {
                        bg: bg.shift(),
                        en: en.shift()
                      }
                    });
                  }
                  return res;
                }
                default:
                  return Promise.reject(new Error('[' + name + '] Unhandled relation model ' + field.relation.model));
              }
            }
            case 'choice':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  return (self[name + 'Bg'] || self[name + 'En']) && {
                      label: {
                        bg: self[name + 'Bg'],
                        en: self[name + 'En']
                      }
                    } || null;
                }
                case 'species':
                {
                  return self[name];
                }
                case 'zone':
                case 'user':
                {
                  return self[name + 'Id'];
                }
                default:
                  return Promise.reject(new Error('[' + name + '] Unhandled relation model ' + field.relation.model));
              }
            }
            case 'json':
            {
              return self[name] && JSON.parse(self[name]);
            }
            default:
              return self[name];
          }
        })).then(function (data) {
          data.id = self.id;
          data.createdAt = self.createdAt;
          data.updatedAt = self.updatedAt;
          return data;
        });
      },

      apiUpdate: function (data) {
        var self = this;

        _.forEach(fields, function (field, name) {
          if (_.isString(field)) field = {type: field};
          switch (field.type) {
            case 'multi':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  if (!_.has(data, name)) return;

                  var val = data[name];

                  if (!val) {
                    self[name + 'Bg'] = null;
                    self[name + 'En'] = null;
                  }
                  if (!_.isArray(val)) val = [val];
                  self[name + 'Bg'] = _.reduce(val, function (sum, v) {
                    return sum + (sum && ' | ' || '') + v.label.bg;
                  }, '');
                  self[name + 'En'] = _.reduce(val, function (sum, v) {
                    return sum + (sum && ' | ' || '') + v.label.en;
                  }, '');

                  break;
                }
                default:
                  throw new Error('[' + name + '] Unsupported relation model ' + field.relation.model);
              }
              break;
            }
            case 'choice':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  if (!_.has(data, name) || !data[name]) return;

                  console.log('saving nomenclature ' + name);
                  self[name + 'Bg'] = data[name].label.bg;
                  self[name + 'En'] = data[name].label.en;
                  break;
                }
                case 'species':
                {
                  if (!_.has(data, name)) return;

                  self[name] = data[name];
                  break;
                }
                case 'user':
                case 'zone':
                {
                  if (!_.has(data, name)) return;

                  self[name + 'Id'] = data[name];
                  break;
                }
                default:
                  throw new Error('[' + name + '] Unsupported relation model ' + field.relation.model);
              }
              break;
            }
            case 'json':
              if (!_.has(data, name)) return;

              self[name] = JSON.stringify(data[name]);
              break;
            default:
              if (!_.has(data, name)) return;

              self[name] = data[name];
              break;
          }
        });

        return this;
      }
    }
  });

  ['beforeCreate', 'beforeUpdate', 'beforeSync', 'beforeSave'].forEach(function(hook){
    model.hook(hook, function(instance) {
      console.log('hook', hook, typeof this, typeof instance);
    });
    model.hook(hook, updateHash);
  });

  return model;

  function updateHash(instance) {
    instance.hash = instance.calculateHash();
  }
};

module.exports.fields = fields;
<commit_msg>added fields for CBM unique hash<commit_after>/**
 * Created by dani on 06.01.16.
 */

'use strict';

var _ = require('lodash');
var Promise = require('bluebird');
var crypto = require('crypto');
var Model = require('../helpers/Model');

var fields = {
  plot: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_sector'}
    }
  },
  visit: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_visit_number'}
    }
  },
  secondaryHabitat: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_habitat'}
    }
  },
  primaryHabitat: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_habitat'}
    }
  },
  distance: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'nomenclature',
      filter: {type: 'cbm_distance'}
    }
  },
  species: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'species',
      filter: {type: 'birds'}
    }
  },
  cloudiness: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_cloud_level'}
    }
  },
  windDirection: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_wind_direction'}
    }
  },
  windSpeed: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_wind_force'}
    }
  },
  rain: {
    type: 'choice',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_rain'}
    }
  },

  count: {
    type: '+int',
    required: true,
    uniqueHash: true,
  },
  endDateTime: {
    type: 'timestamp',
    required: true,
    uniqueHash: true,
  },
  startDateTime: {
    type: 'timestamp',
    required: true,
    uniqueHash: true,
  },
  notes: 'text',
  visibility: '+num',
  mto: 'text',
  cloudsType: 'text',
  temperature: 'num',
  observers: 'text',
  latitude: {
    type: 'num',
    required: true,
    uniqueHash: true,
  },
  longitude: {
    type: 'num',
    required: true,
    uniqueHash: true,
  },

  observationDateTime: {
    type: 'timestamp',
    required: true,
    uniqueHash: true,
  },
  monitoringCode: {
    type: 'text',
    required: true
  },

  zone: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    relation: {
      model: 'zone'
    }
  },

  threats: {
    type: 'multi',
    relation: {
      model: 'nomenclature',
      filter: {type: 'main_threats'}
    }
  },

  user: {
    type: 'choice',
    required: true,
    uniqueHash: true,
    observationDateTime: '2015-12-10T12:15:04Z',
    relation: {
      model: 'user'
    }
  },

  pictures: 'json',
  track: 'text',
};

module.exports = function (sequelize, DataTypes) {
  var fieldsDef = {
    hash: DataTypes.STRING(64)
  };

  _.forEach(fields, function (field, name) {
    if (_.isString(field)) {
      field = {type: field};
    }

    var fd = {
      allowNull: !field.required
    };

    switch (field.type) {
      case 'multi':
      case 'choice':
      {
        switch (field.relation.model) {
          case 'nomenclature':
          {
            fieldsDef[name + 'Bg'] = _.extend({
              type: DataTypes.TEXT
            }, fd);
            fieldsDef[name + 'En'] = _.extend({
              type: DataTypes.TEXT
            }, fd);
            break;
          }
          case 'species':
          {
            fieldsDef[name] = _.extend({
              type: DataTypes.TEXT
            }, fd);
            break;
          }
          case 'zone':
          {
            fieldsDef[name + 'Id'] = _.extend({
              type: field.type === 'multi'
                ? DataTypes.TEXT
                : DataTypes.STRING(10)
            }, fd);
            break;
          }
          case 'user':
          {
            fieldsDef[name + 'Id'] = _.extend({
              type: field.type === 'multi'
                ? DataTypes.TEXT
                : DataTypes.INTEGER
            });
            break;
          }
          default:
            throw new Error('[' + name + '] Unknown relation model ' + field.relation.model);
        }
        break;
      }
      case 'timestamp':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.DATE
        }, fd);
        break;
      }
      case 'float':
      case '+num':
      case 'num':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.FLOAT
        }, fd);
        break;
      }
      case '+int':
      case 'int':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.INTEGER
        }, fd);
        break;
      }
      case 'text':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.TEXT
        }, fd);
        break;
      }
      case 'json':
      {
        fieldsDef[name] = _.extend({
          type: DataTypes.TEXT
        }, fd);
        break;
      }
      default:
        throw new Error('[' + name + '] Unknown field type ' + field.type);

    }
  });

  var model = sequelize.define('FormCBM', fieldsDef, {
    freezeTableName: true,
    indexes: [
      {fields: ['species']},
      {fields: ['zoneId']},
      {fields: ['userId']}
    ],
    classMethods: {
      associate: function (models) {
        models.formCBM.belongsTo(models.species, {as: 'speciesInfo', foreignKey: 'species', targetKey: 'labelLa'});
        models.formCBM.belongsTo(models.zone, {as: 'zone'});
        models.formCBM.belongsTo(models.user, {as: 'user'});
      }
    },
    instanceMethods: {
      calculateHash: Model.generateCalcHash(fields),
      apiData: function (api) {
        var data = {};
        var self = this;
        return Promise.props(_.mapValues(fields, function (field, name) {
          if (_.isString(field)) field = {type: field};
          switch (field.type) {
            case 'multi':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  var res = [];
                  var bg = self[name + 'Bg'] && self[name + 'Bg'].split('|').map(function (val) {
                      return val.trim();
                    }) || [];
                  var en = self[name + 'En'] && self[name + 'En'].split('|').map(function (val) {
                      return val.trim();
                    }) || [];
                  while (bg.length && en.length) {
                    res.push({
                      label: {
                        bg: bg.shift(),
                        en: en.shift()
                      }
                    });
                  }
                  return res;
                }
                default:
                  return Promise.reject(new Error('[' + name + '] Unhandled relation model ' + field.relation.model));
              }
            }
            case 'choice':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  return (self[name + 'Bg'] || self[name + 'En']) && {
                      label: {
                        bg: self[name + 'Bg'],
                        en: self[name + 'En']
                      }
                    } || null;
                }
                case 'species':
                {
                  return self[name];
                }
                case 'zone':
                case 'user':
                {
                  return self[name + 'Id'];
                }
                default:
                  return Promise.reject(new Error('[' + name + '] Unhandled relation model ' + field.relation.model));
              }
            }
            case 'json':
            {
              return self[name] && JSON.parse(self[name]);
            }
            default:
              return self[name];
          }
        })).then(function (data) {
          data.id = self.id;
          data.createdAt = self.createdAt;
          data.updatedAt = self.updatedAt;
          return data;
        });
      },

      apiUpdate: function (data) {
        var self = this;

        _.forEach(fields, function (field, name) {
          if (_.isString(field)) field = {type: field};
          switch (field.type) {
            case 'multi':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  if (!_.has(data, name)) return;

                  var val = data[name];

                  if (!val) {
                    self[name + 'Bg'] = null;
                    self[name + 'En'] = null;
                  }
                  if (!_.isArray(val)) val = [val];
                  self[name + 'Bg'] = _.reduce(val, function (sum, v) {
                    return sum + (sum && ' | ' || '') + v.label.bg;
                  }, '');
                  self[name + 'En'] = _.reduce(val, function (sum, v) {
                    return sum + (sum && ' | ' || '') + v.label.en;
                  }, '');

                  break;
                }
                default:
                  throw new Error('[' + name + '] Unsupported relation model ' + field.relation.model);
              }
              break;
            }
            case 'choice':
            {
              switch (field.relation.model) {
                case 'nomenclature':
                {
                  if (!_.has(data, name) || !data[name]) return;

                  console.log('saving nomenclature ' + name);
                  self[name + 'Bg'] = data[name].label.bg;
                  self[name + 'En'] = data[name].label.en;
                  break;
                }
                case 'species':
                {
                  if (!_.has(data, name)) return;

                  self[name] = data[name];
                  break;
                }
                case 'user':
                case 'zone':
                {
                  if (!_.has(data, name)) return;

                  self[name + 'Id'] = data[name];
                  break;
                }
                default:
                  throw new Error('[' + name + '] Unsupported relation model ' + field.relation.model);
              }
              break;
            }
            case 'json':
              if (!_.has(data, name)) return;

              self[name] = JSON.stringify(data[name]);
              break;
            default:
              if (!_.has(data, name)) return;

              self[name] = data[name];
              break;
          }
        });

        return this;
      }
    }
  });

  ['beforeCreate', 'beforeUpdate', 'beforeSync', 'beforeSave'].forEach(function(hook){
    model.hook(hook, function(instance) {
      console.log('hook', hook, typeof this, typeof instance);
    });
    model.hook(hook, updateHash);
  });

  return model;

  function updateHash(instance) {
    instance.hash = instance.calculateHash();
  }
};

module.exports.fields = fields;
<|endoftext|>


<commit_before>/**
 * Events that can occur when any module is active (Except World. It's special.)
 **/
Events.Global = [
	{ /* The Thief */
		title: _('The Thief'),
		isAvailable: function() {
			return (Engine.activeModule == Room || Engine.activeModule == Outside) && $SM.get('game.thieves') == 1;
		},
		scenes: {
			'start': {
				text: [
					_('the villagers haul a filthy man out of the store room.'),
					_("say his folk have been skimming the supplies."),
					_('say he should be strung up as an example.')
				],
				notification: _('a thief is caught'),
				buttons: {
					'kill': {
						text: _('hang him'),
						nextScene: {1: 'hang'}
					},
					'spare': {
						text: _('spare him'),
						nextScene: {1: 'spare'}
					}
				}
			},
			'hang': {
				text: [
			       _('the villagers hang the thief high in front of the store room.'),
			       _('the point is made. in the next few days, the missing supplies are returned.')
		        ],
		        onLoad: function() {
		        	$SM.set('game.thieves', 2);
		        	$SM.remove('income.thieves');
		        	$SM.addM('stores', $SM.get('game.stolen'));
		        },
		        buttons: {
					'leave': {
						text: _('leave'),
						nextScene: 'end'
					}
				}
			},
			'spare': {
				text: [
			       _("the man says he's grateful. says he won't come around any more."),
			       _("shares what he knows about sneaking before he goes.")
		        ],
		        onLoad: function() {
		        	$SM.set('game.thieves', 2);
		        	$SM.remove('income.thieves');
		        	$SM.addPerk('stealthy');
		        },
		        buttons: {
		        	'leave': {
		        		text: _('leave'),
		        		nextScene: 'end'
		        	}
		        }
			}
		}
	}
];<commit_msg>Fixing whitespace conformity in global.js<commit_after>/**
 * Events that can occur when any module is active (Except World. It's special.)
 **/
Events.Global = [
	{ /* The Thief */
		title: _('The Thief'),
		isAvailable: function() {
			return (Engine.activeModule == Room || Engine.activeModule == Outside) && $SM.get('game.thieves') == 1;
		},
		scenes: {
			'start': {
				text: [
					_('the villagers haul a filthy man out of the store room.'),
					_("say his folk have been skimming the supplies."),
					_('say he should be strung up as an example.')
				],
				notification: _('a thief is caught'),
				buttons: {
					'kill': {
						text: _('hang him'),
						nextScene: {1: 'hang'}
					},
					'spare': {
						text: _('spare him'),
						nextScene: {1: 'spare'}
					}
				}
			},
			'hang': {
				text: [
					_('the villagers hang the thief high in front of the store room.'),
					_('the point is made. in the next few days, the missing supplies are returned.')
				],
				onLoad: function() {
					$SM.set('game.thieves', 2);
					$SM.remove('income.thieves');
					$SM.addM('stores', $SM.get('game.stolen'));
				},
				buttons: {
					'leave': {
						text: _('leave'),
						nextScene: 'end'
					}
				}
			},
			'spare': {
				text: [
					_("the man says he's grateful. says he won't come around any more."),
					_("shares what he knows about sneaking before he goes.")
				],
				onLoad: function() {
					$SM.set('game.thieves', 2);
					$SM.remove('income.thieves');
					$SM.addPerk('stealthy');
				},
				buttons: {
					'leave': {
						text: _('leave'),
						nextScene: 'end'
					}
				}
			}
		}
	}
];
<|endoftext|>


<commit_before>"use strict";

var types = require('./types');

var Nothing = types.Nothing;
var Anything = types.Anything;
var Literal = types.Literal;
var FStruct = types.FStruct;
var World = types.World;
var Variable = types.Variable;
var Lattice = types.Lattice;
var Set = types.Set;
var eq = types.eq;
var copy = types.copy;

// NOTE: worlds must always be in top-level objects. Otherwise everything
// breaks.

/**
 * Creates a new unification context for x and y.
 *
 * @param {Borjes} x
 * @param {Borjes} y
 * @param {Object} defaults
 */
function UniCtx (x, y, defaults) {
    var nw, lm = {}, rm = {};
    if (defaults !== undefined) {
        nw = defaults.newworld ? copy(defaults.newworld) : undefined;
        if (defaults.leftmap) {
            var olm = defaults.leftmap;
            lm._w = olm._w;
            Object.keys(olm).forEach(function (k) {
                if (k !== '_w' && k !== '_nw') {
                    lm[k] = olm[k];
                }
            });
        }
        if (defaults.rightmap) {
            var orm = defaults.rightmap;
            rm._w = orm._w;
            Object.keys(orm).forEach(function (k) {
                if (k !== '_w' && k !== '_nw') {
                    rm[k] = orm[k];
                }
            });
        }
    }
    nw = nw || World();
    lm._nw = nw;
    rm._nw = nw;
    lm._w = lm._w || x.borjes_bound;
    rm._w = rm._w || y.borjes_bound;
    /**
     * The current context of a unification process.
     *
     * @typedef UniCtx
     * @param {World} newworld - this is a private parameter for the recursive
     * call of unify. neworld is a world which unifies the worlds from x and y.
     * @param {WorldMap} leftmap - a mapping from the world of x into newworld.
     * @param {WorldMap} rightmap - a mapping from the world of y into newworld.
     * @param stack
     */
    return {
        newworld: nw,
        leftmap: lm,
        rightmap: rm,
        stack: []
    };
}

/**
 * Returns all possible unifications of x and y.
 *
 * @param {Borjes} x
 * @param {Borjes} y
 * @param {boolean} [as_array=false] - if true, the results are always returned
 * @param {Object} defaults - {newworld, leftmap, rightmap}
 * as an array.
 * @return {Nothing|Borjes|Borjes[]}
 */
function unifyAll (x, y, as_array, defaults) {
    var r = [];
    var stack = [];
    do {
        var ux = UniCtx(x, y, defaults);
        ux.stack = stack;
        var u = unify(x, y, ux);
        if (!eq(u, Nothing)) {
            if (x.borjes_bound || y.borjes_bound) {
                World.bind(ux.newworld, u);
                u.ux = ux;
            }
            r.push(u);
        }
        stack = ux.stack;
    } while (stack.length > 0);
    if (as_array || r.length > 1) { return r; }
    if (r.length === 1) { return r[0]; }
    return Nothing;
}

/**
 * Unification takes two Borjes objects, and returns their most general unifier.
 *
 * @param {Borjes} x
 * @param {Borjes} y
 * @param {UniCtx} [ux] - the context of the current unification (for recursive
 * calling).
 * @return {Borjes|Nothing} returns the mgu of x and y, with a unifying world
 * bound if either of them had a world bound. If the mgu doesn't exist
 * (unification is not possible/fails) then Nothing is returned.
 */
function unify (x, y, ux) {
    if (x !== undefined && x.borjes === 'disjunct') {
        return unifyDisj(x, y, ux, true);
    }
    if (y !== undefined && y.borjes === 'disjunct') {
        return unifyDisj(x, y, ux, false);
    }
    if (x !== undefined && x.borjes === 'variable') {
        return unifyVar(x, y, ux, true);
    }
    if (y !== undefined && y.borjes === 'variable') {
        return unifyVar(x, y, ux, false);
    }
    if (x === undefined) { return copy(y, ux.rightmap); }
    if (y === undefined) { return copy(x, ux.leftmap); }
    if (eq(x, y)) {
        return copy(x); // only primitive values can be eq, so no maps necessary
    }
    if (eq(x, Nothing) || eq(y, Nothing)) {
        return Nothing;
    }
    if (eq(x, Anything)) {
        return copy(y, ux.rightmap);
    }
    if (eq(y, Anything)) {
        return copy(x, ux.leftmap);
    }
    if (x.borjes === 'latticeel' && y.borjes === 'latticeel') {
        return Lattice.meet(x, y);
    }
    if (x.borjes === 'fstruct' && y.borjes === 'fstruct') {
        return unifyFS(x, y, ux);
    }
    if ((x.borjes === 'tfstruct' && y.borjes === 'tfstruct')
        || (x.borjes === 'tfstruct' && y.borjes === 'fstruct')
        || (x.borjes === 'fstruct' && y.borjes === 'tfstruct')) {
        return unifyTFS(x, y, ux);
    }
    if (x.borjes === 'list' && y.borjes === 'list') {
        return unifyLists(x, y, ux);
    }
    if (x.borjes === 'set_sum' && y.borjes === 'set') {
        return unifyDSum(x, y, ux, true);
    }
    if (y.borjes === 'set_sum' && x.borjes === 'set') {
        return unifyDSum(x, y, ux, false);
    }
    return Nothing;
}

/**
 * Unifies two feature structures.
 *
 * @see unify for params.
 */
function unifyFS (x, y, ux) {
    var r = FStruct();
    var unified = {};
    for (var i = 0; i<x.f.length; i++) {
        var f = x.f[i];
        var u = unify(x.v[f], y.v[f], ux);
        if (eq(u, Nothing)) { return Nothing; }
        r.f.push(f);
        r.v[f] = u;
        unified[f] = true;
    }
    for (var j = 0; j<y.f.length; j++) {
        var f = y.f[j];
        if (!unified[f]) {
            r.f.push(f);
            r.v[f] = unify(x.v[f], y.v[f], ux);
        }
    }
    return r;
}

/**
 * Unifies a typed feature structure with another (possibly typed) feature
 * structure.
 *
 * @param {TFS} x
 * @param {TFS|FStruct} y
 * @see unify for ux
 */
function unifyTFS (x, y, ux) {
    var type = unify(x.type, y.type, ux);
    if (type === Nothing) { return Nothing; }
    var mgu = unifyFS(x, y, ux);
    if (mgu === Nothing) { return Nothing; }
    mgu.borjes = 'tfstruct';
    mgu.type = type;
    return mgu;
}

/**
 * Unifies a variable with another object (possibly binding its value).
 *
 * @param {Variable} x
 * @param {boolean} left - if true, x is variable, otherwise y is.
 * @see unify for the rest of the params.
 */
function unifyVar (x, y, ux, left) {
    var v;
    var map = left ? ux.leftmap : ux.rightmap;
    var who = left ? x.index : y.index;
    if (map[who] !== undefined) {
        v = World.get(ux.newworld, map[who]);
    } else {
        v = World.get(map._w, who);
    }
    var u = left ? unify(v, y, ux) : unify(x, v, ux);
    if (u !== undefined && eq(u, Nothing)) { return Nothing; }
    var r;
    if (map[who] !== undefined) {
        r = copy(left?x:y, map);
        World.set(ux.newworld, map[who], u);
    } else {
        r = Variable(ux.newworld, u);
        map[who] = r.index;
    }
    return r;
}

/**
 * Unifies two non-empty lists.
 *
 * @see unify for the params.
 */
function unifyLists (x, y, ux) {
    var f = unify(x.first, y.first, ux);
    if (eq(f, Nothing)) { return Nothing; }
    var r = unify(x.rest, y.rest, ux);
    if (eq(r, Nothing)) { return Nothing; }
    return types.List(f, r);
}

/**
 * Unifies two disjuncts.
 *
 * @param {boolean} left - if true, x is a disjunct, otherwise y is.
 * @see unify for the params.
 */
function unifyDisj (x, y, ux, left) {
    var st = ux.stack;
    var who = left ? x : y;
    var uip = who.borjes_uip;
    var ix, u;
    if (!uip) {
        who.borjes_uip = { at: 0 };
        uip = who.borjes_uip;
        st.push(uip);
    }
    var u = left ? unify(x.a[uip.at], y, ux)
                 : unify(x, y.a[uip.at], ux);
    if (st[st.length-1] === uip) {
        uip.at += 1;
        if (uip.at >= who.a.length) {
            st.pop();
            delete who.borjes_uip;
        }
    }
    return u;
}

/**
 * Unifies a direct sum with a set, decomposing it.
 *
 * @param {boolean} left - if true, x is a direct sum, otherwise y is.
 * @see unify for the params.
 */
function unifyDSum (x, y, ux, left) {
    var st = ux.stack;
    var who = left ? x : y;
    var list = left ? y : x;
    var uip = who.borjes_uip;
    var ix, u;
    if (!uip) {
        who.borjes_uip = { at: 0 };
        uip = who.borjes_uip;
        st.push(uip);
    }
    var u = left ? unify(x.el, y.e[uip.at], ux)
                 : unify(x.e[uip.at], y.el, ux);
    if (st[st.length-1] === uip) {
        uip.at += 1;
        if (uip.at >= list.e.length) {
            st.pop();
            delete who.borjes_uip;
        }
    }
    if (eq(u, Nothing)) { return Nothing; }
    var rem = [];
    var map = left?ux.rightmap:ux.leftmap;
    for (var i=0; i<list.e.length; i++) {
        if (i!==uip.at-1) {
            rem.push(copy(list.e[i], map));
        }
    }
    var rest = left ? unify(x.rest, Set.apply(null, rem), ux)
                    : unify(Set.apply(null, rem), y.rest, ux);
    if (eq(rest, Nothing)) { return Nothing; }
    return Set.sum(u, rest);
}

module.exports = unifyAll;
<commit_msg>Revert not attaching worlds sometimes.<commit_after>"use strict";

var types = require('./types');

var Nothing = types.Nothing;
var Anything = types.Anything;
var Literal = types.Literal;
var FStruct = types.FStruct;
var World = types.World;
var Variable = types.Variable;
var Lattice = types.Lattice;
var Set = types.Set;
var eq = types.eq;
var copy = types.copy;

// NOTE: worlds must always be in top-level objects. Otherwise everything
// breaks.

/**
 * Creates a new unification context for x and y.
 *
 * @param {Borjes} x
 * @param {Borjes} y
 * @param {Object} defaults
 */
function UniCtx (x, y, defaults) {
    var nw, lm = {}, rm = {};
    if (defaults !== undefined) {
        nw = defaults.newworld ? copy(defaults.newworld) : undefined;
        if (defaults.leftmap) {
            var olm = defaults.leftmap;
            lm._w = olm._w;
            Object.keys(olm).forEach(function (k) {
                if (k !== '_w' && k !== '_nw') {
                    lm[k] = olm[k];
                }
            });
        }
        if (defaults.rightmap) {
            var orm = defaults.rightmap;
            rm._w = orm._w;
            Object.keys(orm).forEach(function (k) {
                if (k !== '_w' && k !== '_nw') {
                    rm[k] = orm[k];
                }
            });
        }
    }
    nw = nw || World();
    lm._nw = nw;
    rm._nw = nw;
    lm._w = lm._w || x.borjes_bound;
    rm._w = rm._w || y.borjes_bound;
    /**
     * The current context of a unification process.
     *
     * @typedef UniCtx
     * @param {World} newworld - this is a private parameter for the recursive
     * call of unify. neworld is a world which unifies the worlds from x and y.
     * @param {WorldMap} leftmap - a mapping from the world of x into newworld.
     * @param {WorldMap} rightmap - a mapping from the world of y into newworld.
     * @param stack
     */
    return {
        newworld: nw,
        leftmap: lm,
        rightmap: rm,
        stack: []
    };
}

/**
 * Returns all possible unifications of x and y.
 *
 * @param {Borjes} x
 * @param {Borjes} y
 * @param {boolean} [as_array=false] - if true, the results are always returned
 * @param {Object} defaults - {newworld, leftmap, rightmap}
 * as an array.
 * @return {Nothing|Borjes|Borjes[]}
 */
function unifyAll (x, y, as_array, defaults) {
    var r = [];
    var stack = [];
    do {
        var ux = UniCtx(x, y, defaults);
        ux.stack = stack;
        var u = unify(x, y, ux);
        if (!eq(u, Nothing)) {
            World.bind(ux.newworld, u);
            u.ux = ux;
            r.push(u);
        }
        stack = ux.stack;
    } while (stack.length > 0);
    if (as_array || r.length > 1) { return r; }
    if (r.length === 1) { return r[0]; }
    return Nothing;
}

/**
 * Unification takes two Borjes objects, and returns their most general unifier.
 *
 * @param {Borjes} x
 * @param {Borjes} y
 * @param {UniCtx} [ux] - the context of the current unification (for recursive
 * calling).
 * @return {Borjes|Nothing} returns the mgu of x and y, with a unifying world
 * bound if either of them had a world bound. If the mgu doesn't exist
 * (unification is not possible/fails) then Nothing is returned.
 */
function unify (x, y, ux) {
    if (x !== undefined && x.borjes === 'disjunct') {
        return unifyDisj(x, y, ux, true);
    }
    if (y !== undefined && y.borjes === 'disjunct') {
        return unifyDisj(x, y, ux, false);
    }
    if (x !== undefined && x.borjes === 'variable') {
        return unifyVar(x, y, ux, true);
    }
    if (y !== undefined && y.borjes === 'variable') {
        return unifyVar(x, y, ux, false);
    }
    if (x === undefined) { return copy(y, ux.rightmap); }
    if (y === undefined) { return copy(x, ux.leftmap); }
    if (eq(x, y)) {
        return copy(x); // only primitive values can be eq, so no maps necessary
    }
    if (eq(x, Nothing) || eq(y, Nothing)) {
        return Nothing;
    }
    if (eq(x, Anything)) {
        return copy(y, ux.rightmap);
    }
    if (eq(y, Anything)) {
        return copy(x, ux.leftmap);
    }
    if (x.borjes === 'latticeel' && y.borjes === 'latticeel') {
        return Lattice.meet(x, y);
    }
    if (x.borjes === 'fstruct' && y.borjes === 'fstruct') {
        return unifyFS(x, y, ux);
    }
    if ((x.borjes === 'tfstruct' && y.borjes === 'tfstruct')
        || (x.borjes === 'tfstruct' && y.borjes === 'fstruct')
        || (x.borjes === 'fstruct' && y.borjes === 'tfstruct')) {
        return unifyTFS(x, y, ux);
    }
    if (x.borjes === 'list' && y.borjes === 'list') {
        return unifyLists(x, y, ux);
    }
    if (x.borjes === 'set_sum' && y.borjes === 'set') {
        return unifyDSum(x, y, ux, true);
    }
    if (y.borjes === 'set_sum' && x.borjes === 'set') {
        return unifyDSum(x, y, ux, false);
    }
    return Nothing;
}

/**
 * Unifies two feature structures.
 *
 * @see unify for params.
 */
function unifyFS (x, y, ux) {
    var r = FStruct();
    var unified = {};
    for (var i = 0; i<x.f.length; i++) {
        var f = x.f[i];
        var u = unify(x.v[f], y.v[f], ux);
        if (eq(u, Nothing)) { return Nothing; }
        r.f.push(f);
        r.v[f] = u;
        unified[f] = true;
    }
    for (var j = 0; j<y.f.length; j++) {
        var f = y.f[j];
        if (!unified[f]) {
            r.f.push(f);
            r.v[f] = unify(x.v[f], y.v[f], ux);
        }
    }
    return r;
}

/**
 * Unifies a typed feature structure with another (possibly typed) feature
 * structure.
 *
 * @param {TFS} x
 * @param {TFS|FStruct} y
 * @see unify for ux
 */
function unifyTFS (x, y, ux) {
    var type = unify(x.type, y.type, ux);
    if (type === Nothing) { return Nothing; }
    var mgu = unifyFS(x, y, ux);
    if (mgu === Nothing) { return Nothing; }
    mgu.borjes = 'tfstruct';
    mgu.type = type;
    return mgu;
}

/**
 * Unifies a variable with another object (possibly binding its value).
 *
 * @param {Variable} x
 * @param {boolean} left - if true, x is variable, otherwise y is.
 * @see unify for the rest of the params.
 */
function unifyVar (x, y, ux, left) {
    var v;
    var map = left ? ux.leftmap : ux.rightmap;
    var who = left ? x.index : y.index;
    if (map[who] !== undefined) {
        v = World.get(ux.newworld, map[who]);
    } else {
        v = World.get(map._w, who);
    }
    var u = left ? unify(v, y, ux) : unify(x, v, ux);
    if (u !== undefined && eq(u, Nothing)) { return Nothing; }
    var r;
    if (map[who] !== undefined) {
        r = copy(left?x:y, map);
        World.set(ux.newworld, map[who], u);
    } else {
        r = Variable(ux.newworld, u);
        map[who] = r.index;
    }
    return r;
}

/**
 * Unifies two non-empty lists.
 *
 * @see unify for the params.
 */
function unifyLists (x, y, ux) {
    var f = unify(x.first, y.first, ux);
    if (eq(f, Nothing)) { return Nothing; }
    var r = unify(x.rest, y.rest, ux);
    if (eq(r, Nothing)) { return Nothing; }
    return types.List(f, r);
}

/**
 * Unifies two disjuncts.
 *
 * @param {boolean} left - if true, x is a disjunct, otherwise y is.
 * @see unify for the params.
 */
function unifyDisj (x, y, ux, left) {
    var st = ux.stack;
    var who = left ? x : y;
    var uip = who.borjes_uip;
    var ix, u;
    if (!uip) {
        who.borjes_uip = { at: 0 };
        uip = who.borjes_uip;
        st.push(uip);
    }
    var u = left ? unify(x.a[uip.at], y, ux)
                 : unify(x, y.a[uip.at], ux);
    if (st[st.length-1] === uip) {
        uip.at += 1;
        if (uip.at >= who.a.length) {
            st.pop();
            delete who.borjes_uip;
        }
    }
    return u;
}

/**
 * Unifies a direct sum with a set, decomposing it.
 *
 * @param {boolean} left - if true, x is a direct sum, otherwise y is.
 * @see unify for the params.
 */
function unifyDSum (x, y, ux, left) {
    var st = ux.stack;
    var who = left ? x : y;
    var list = left ? y : x;
    var uip = who.borjes_uip;
    var ix, u;
    if (!uip) {
        who.borjes_uip = { at: 0 };
        uip = who.borjes_uip;
        st.push(uip);
    }
    var u = left ? unify(x.el, y.e[uip.at], ux)
                 : unify(x.e[uip.at], y.el, ux);
    if (st[st.length-1] === uip) {
        uip.at += 1;
        if (uip.at >= list.e.length) {
            st.pop();
            delete who.borjes_uip;
        }
    }
    if (eq(u, Nothing)) { return Nothing; }
    var rem = [];
    var map = left?ux.rightmap:ux.leftmap;
    for (var i=0; i<list.e.length; i++) {
        if (i!==uip.at-1) {
            rem.push(copy(list.e[i], map));
        }
    }
    var rest = left ? unify(x.rest, Set.apply(null, rem), ux)
                    : unify(Set.apply(null, rem), y.rest, ux);
    if (eq(rest, Nothing)) { return Nothing; }
    return Set.sum(u, rest);
}

module.exports = unifyAll;
<|endoftext|>