<commit_before>(function(){

NewsFeedController = Backbone.View.extend({
	el: $("div .middle"),

	events: {
		"click #showAllPosts": "toggleAllPosts",
		"click #showUnreadPosts": "toggleUnreadPosts",
		"click #addNewFeedLink": "addNewFeed",
		"click #removeFeedLink": "removeFeed",
		"click #markAllReadLink": "markAllRead",
		"click #importFeedsLink": "showImportWindow",

		// Prevents full page refresh when using pushState.
		// Oddly, Chrome does this but Safari doesn't (!), even though both support pushState.
		"click .feedcategory a": "manualNavigate",
		"click .feed a": "manualNavigate",
	},

	manualNavigate: function(evt) {
		evt.preventDefault();

		if(typeof window.orientation !== 'undefined')
		{
			// hide all menus on mobile device.
			hideAllMenus();
		}

		var hash = evt.target.hash;
		if (hash == undefined)
		{
			// we clicked on the unread counter
			var href = $(evt.target).parent().attr("href");
			hash = href.substring(href.indexOf("#"));
		}
		AppRouter.navigate(hash, {trigger: true});
	},

	initialize: function() {
		if(typeof window.orientation !== 'undefined')
		{
			// enable hiding of all menus on mobile device.
			$(".feedmenu").click(function() {
				hideAllMenus();
			});
		}

		this.enableInfiniteScrolling = false;
		this.clearPosts();
		this.showHideMenuOptions();

		// Set up infinite scrolling.
		var self = this;
		self.currentPostCount = 0;
		var win = $(window);
		var doc = $(document);
		var throttledFn = _.throttle(function () {
			if (win.scrollTop() >= doc.height() - win.height() - 10) {
				if (self.enableInfiniteScrolling && self.articleCollection)
				{
					self.enableInfiniteScrolling = false;
					self.articleCollection.currentPage += 1;
					self.articleCollection.fetch({
						success: function(collection, response, options) {
							if (self.articleCollection.length != self.currentPostCount)
							{
								self.enableInfiniteScrolling = true;
								self.currentPostCount = self.articleCollection.length;
							}
						},
						error: function(x,y,z) { y.url = self.articleCollection.urlBase; self.collectionFetchErrorHandler(x,y,z); },
						reset: false,
						remove: false
					});
				}
   			}
		}, 1000);
		$(window).scroll(throttledFn);

		// Get parameters from local storage, if available.
		if (typeof(Storage) !== "undefined")
		{
			if (!localStorage.showOnlyUnread)
			{
				localStorage.showOnlyUnread = true;
			}
			this.showOnlyUnread = localStorage.showOnlyUnread;

			// Only update HTML if we're showing all, since the HTML is hardcoded as unread only.
			// Note: why is it making things into strings? :(
			if (this.showOnlyUnread == "false")
			{
				$("#showAllPosts").unwrap();
				$("#showUnreadPosts").wrap("<a />");
			}

			// Reset width of feed list from last session.
			var listWidth = $(".leftcol").width;
			if (localStorage.feedListWidth !== "undefined")
			{
				listWidth = 1 * localStorage.feedListWidth; // typecast to int
			}
			$(".leftcol").width(listWidth);
			$(".rightcol").css("margin-left", listWidth);
		}
		else
		{
			// Browser doesn't support local storage, default to unread only.
			this.showOnlyUnread = true;
		}

		this.listenTo(NewsFeeds, 'add', this.addOneFeed);
		this.listenTo(NewsFeeds, 'reset', this.addAllFeeds);
		this.listenTo(NewsFeeds, 'sort', this.addAllFeeds);

		// Perform initial fetch from server.
		NewsFeeds.reset(bootstrappedFeeds);
		self.updateFeedCounts();
		NewsFeeds.each(function(x) {
			self.stopListening(x, 'change:numUnread');
			self.listenTo(x, 'change:numUnread', function() { self.updateFeedCounts(); });
		});

		// Update feed counts every five minutes.
		var self = this;
		window.setInterval(function() { self.updateFeeds(); }, 1000 * (60 * 5));

		// make the feed list resizable.
		this.makeDraggable();
	},

	showImportWindow: function() {
		this.UploadForm = new FileUploadForm();
		this.UploadForm.show();
	},

	// We're not using JQuery's because of the weird way we've set up the CSS.
	makeDraggable: function() {
		var self = this;

		var mouseMoveHandler = function(event) {
			event.preventDefault();
			var newWidth = self._startWidth + (event.pageX - self._startX);
			$(".leftcol").width(newWidth);
			$(".rightcol").css("margin-left", newWidth);

			if (typeof(Storage) !== "undefined")
			{
				localStorage.feedListWidth = newWidth;
			}
		};

		var mouseUpHandler = function(event) {
			$(document).unbind("mousemove", mouseMoveHandler);
			$(document).unbind("mouseup", mouseUpHandler);
		};

		var mouseDownHandler = function(event) {
			event.preventDefault();
			self._startX = event.pageX;
			self._startWidth = $(".leftcol").width();

			$(document).mousemove(mouseMoveHandler);
			$(document).mouseup(mouseUpHandler);
		};

		$(".leftcol").mousedown(mouseDownHandler);
	},

	updateFeeds: function() {
		var self = this;

		NewsFeeds.fetch({
			success: function(collection, response, options) {
				self.updateFeedCounts();
				NewsFeeds.each(function(x) {
					self.stopListening(x, 'change:numUnread');
					self.listenTo(x, 'change:numUnread', function() { self.updateFeedCounts(); });
				});
			},
			error: function(x,y,z) {  y.url = NewsFeeds.urlBase; self.collectionFetchErrorHandler(x,y,z); }
		});
	},

	updateFeedCounts: function() {
		// add up all of the unread posts and put under "All".
		var total_unread = NewsFeeds.reduce(function(memo, feed) { return memo + feed.get("numUnread"); }, 0);
		$("#allCount").text(total_unread);
		if (total_unread == 0) {
			$("#allCount").addClass("hide-element");
		} else {
			$("#allCount").removeClass("hide-element");
		}

		// also update count in the title
		document.title = document.title.replace(/^\(\d+\)\s*/, "");
		if (total_unread > 0)
		{
			document.title = "(" + total_unread + ") " + document.title;
		}
	},

	selectFeed: function(feed) {
		this.enableInfiniteScrolling = false;
		this.clearPosts();

		$(".welcomeblock").addClass("hide-element");
		$("#homeEntry").removeClass("selectedfeed");
		$("#allFeedEntry").removeClass("selectedfeed");
		if (this.selectedFeed) {
			this.selectedFeed.$el.removeClass("selectedfeed");
		}
		this.selectedFeed = feed;

		this.articleCollection = new NewsArticleCollection([]);
		if (feed) {
			this.articleCollection.urlBase = '/feeds/' + feed.model.id + "/posts?unread_only=" + this.showOnlyUnread;
			feed.$el.addClass("selectedfeed");

			// set up correct website URL that the feed provided.
			$("#feedsiteurl").attr("href", feed.model.get("feed").link);
		} else {
			this.articleCollection.urlBase = "/posts?unread_only=" + this.showOnlyUnread;
			this.$("#allFeedEntry").addClass("selectedfeed");
		}

		this.showHideMenuOptions();

		this.listenTo(this.articleCollection, 'add', this.addOneArticle);
		this.listenTo(this.articleCollection, 'reset', this.addAllArticles);

		var self = this;
		this.articleCollection.fetch({
			success: function(collection, response, options) {
				self.enableInfiniteScrolling = true;
				self.currentPostCount = collection.length;
			},
			error: function(x,y,z) { y.url = self.articleCollection.urlBase; self.collectionFetchErrorHandler(x,y,z); }
		});
	},

	sortFeeds: function() {
		this.addAllFeeds();
	},

	markAllRead: function() {
		this.articleCollection.each(function(article) {
			if (article.get("unread")) {
				article.set("unread", false);
			}
		}, this);
	},

	addOneFeed: function(feed) {
		var newView = new NewsFeedView({model: feed});
		var renderedView = newView.render();
		feed.view = newView;
		$("#allfeeds").append(renderedView.el);
		if (this.selectedFeed && this.selectedFeed.model.id == feed.id)
		{
			renderedView.$el.addClass("selectedfeed");
			this.selectedFeed = newView;
		}
	},

	addAllFeeds: function() {
		$("#allfeeds").children().remove();
		NewsFeeds.each(this.addOneFeed, this);
	},

	addOneArticle: function(feed) {
		var newView = new NewsArticleView({model: feed});
		this.$("#postlist").append(newView.render().el);
	},

	addAllArticles: function() {
		this.$("#postlist").children().remove();
		this.articleCollection.each(this.addOneArticle, this);
	},

	clearPosts: function() {
		this.enableInfiniteScrolling = false;

		if (this.articleCollection) {
			// clear event handlers
			this.articleCollection.stopListening();
			this.articleCollection.reset();
			this.articleCollection = null;

			// remove posts
			this.$("#postlist").empty();
			self.currentPostCount = 0;
		}

		// set selected to Home
		$(".selectedfeed").removeClass("selectedfeed");
		$("#allFeedEntry").removeClass("selectedfeed");
		$("#homeEntry").addClass("selectedfeed");
		$(".welcomeblock").removeClass("hide-element");

		this.selectedFeed = null;
		this.showHideMenuOptions();
	},

	showHideMenuOptions: function() {
		//$(".authonly").addClass("hide-element");
		$(".feedonly").addClass("hide-element");
		$(".allonly").addClass("hide-element");

		if (this.selectedFeed) {
			$(".feedonly").removeClass("hide-element");
		} else {
			if ($("#allFeedEntry").hasClass("selectedfeed")) {
				$(".allonly").removeClass("hide-element");
			}
		}
	},

	toggleAllPosts: function() {
		$("#showAllPosts").unwrap();
		$("#showUnreadPosts").wrap("<a />");

		this.showOnlyUnread = false;
		if (typeof(Storage) !== "undefined")
		{
			localStorage.showOnlyUnread = this.showOnlyUnread;
		}
		this.selectFeed(this.selectedFeed);
	},

	toggleUnreadPosts: function() {
		$("#showUnreadPosts").unwrap();
		$("#showAllPosts").wrap("<a />");

		this.showOnlyUnread = true;
		if (typeof(Storage) !== "undefined")
		{
			localStorage.showOnlyUnread = this.showOnlyUnread;
		}
		this.selectFeed(this.selectedFeed);
	},

	addNewFeed: function() {
		// TODO: we probably want something a lot nicer than JS prompt().
		var feedUrl = prompt("Enter the URL of the feed or website that you wish to subscribe to.");

		if (feedUrl) {
			var self = this;
			NewsFeeds.addFeed(feedUrl, function() {
				self.updateFeedCounts();
			});
		}
	},

	addBulkFeeds: function(feeds) {
	    var index = 0;
	    var self = this;
		var feedFn = function() {
			if (index < feeds.length)
			{
				$("#currentImportingFeed").text(index);

				var url = feeds[index];
				var nextFn = function() { index++;  self.updateFeedCounts(); feedFn(); };
				NewsFeeds.addFeed(url, nextFn, nextFn);
			}
			else
			{
				$("#currentImportingFeed").text(index + 1);
				$("#importFeedsLink").show();
				$("#importProgress").addClass("hide-element");

				noty({ text: "Import complete.", layout: "topRight", timeout: 2000, dismissQueue: true, type: "success" });
			}
		};

		if (feeds.length > 0)
		{
			$("#totalImportCount").text(feeds.length);
			$("#importFeedsLink").hide();
			$("#importProgress").removeClass("hide-element");
			feedFn();
		}
	},

	removeFeed: function() {
		// TODO: we probably want something a lot nicer than JS confirm()
		var confirmed = confirm("Are you sure you want to unsubscribe from this feed/website?");
		if (confirmed) {
			var feed = this.selectedFeed;
			feed.$el.remove();
			this.selectFeed(null);
			NewsFeeds.remove(feed.model);
			feed.model.destroy();
			this.updateFeedCounts();
		}
	},

	globalAjaxErrorHandler: function(xhr, status, errorThrown) {
		this.showErrorBasedOnResponse(xhr);
	},

	collectionFetchErrorHandler: function(collection, response, options) {
		// Always GET, but response object does not provide it.
		response.type = "GET";
		this.showErrorBasedOnResponse(response);
	},

	showErrorBasedOnResponse: function(xhr) {
		var errorText = "Communications error with the server. Please try again.";
		var url = xhr.url;
		var type = xhr.type.toUpperCase();

		// Custom errors depending on the situation.
		if (xhr.status == 422) {
			errorText = "Invalid input. Please verify and try again.";
		} else if (xhr.status == 401) {
			// Session expired, force a relogin.
			location.refresh();
		} else if (/\/feeds\/?/.test(url) && type == "POST") {
			errorText = "Could not add feed. Please try again.";
		} else if (/\/feeds\/\d+\/?/.test(url) && type == "DELETE") {
			errorText = "Could not delete feed. Please try again.";
		} else if (/\/feeds\/\d+\/posts\/?/.test(url) && type == "GET") {
			errorText = "Could not retrieve posts. Please try again.";
		} else if (/\/feeds\/\d+\/posts\/\d+\/?/.test(url) && type == "DELETE") {
			errorText = "Could not mark post as read. Please try again.";
		} else if (/\/feeds\/\d+\/posts\/\d+\/?/.test(url) && type == "PUT") {
			errorText = "Could not mark post as read. Please try again.";
		} else if (/\/posts\/?/.test(url) && type == "GET") {
			errorText = "Could not retrieve posts. Please try again.";
		} else if (/\/posts\/\d+\/?/.test(url) && type == "DELETE") {
			errorText = "Could not mark post as read. Please try again.";
		} else if (/\/posts\/\d+\/?/.test(url) && type == "PUT") {
			errorText = "Could not mark post as read. Please try again.";
		}

		noty({ text: errorText, layout: "topRight", timeout: 2000, dismissQueue: true, type: "error" });
	}
});

}).call(this);<commit_msg>allow user to reload feed by clicking on the feed name<commit_after>(function(){

NewsFeedController = Backbone.View.extend({
	el: $("div .middle"),

	events: {
		"click #showAllPosts": "toggleAllPosts",
		"click #showUnreadPosts": "toggleUnreadPosts",
		"click #addNewFeedLink": "addNewFeed",
		"click #removeFeedLink": "removeFeed",
		"click #markAllReadLink": "markAllRead",
		"click #importFeedsLink": "showImportWindow",

		// Prevents full page refresh when using pushState.
		// Oddly, Chrome does this but Safari doesn't (!), even though both support pushState.
		"click .feedcategory a": "manualNavigate",
		"click .feed a": "manualNavigate",
	},

	manualNavigate: function(evt) {
		evt.preventDefault();

		if(typeof window.orientation !== 'undefined')
		{
			// hide all menus on mobile device.
			hideAllMenus();
		}

		var hash = evt.target.hash;
		if (hash == undefined)
		{
			// we clicked on the unread counter
			var href = $(evt.target).parent().attr("href");
			hash = href.substring(href.indexOf("#"));
		}

		// HACK: trick the router into thinking something's changed.
		// Otherwise, clicking on a link we're already on won't
		// reload the page.
		Backbone.history.fragment = "";
		AppRouter.navigate(hash, {trigger: true});
	},

	initialize: function() {
		if(typeof window.orientation !== 'undefined')
		{
			// enable hiding of all menus on mobile device.
			$(".feedmenu").click(function() {
				hideAllMenus();
			});
		}

		this.enableInfiniteScrolling = false;
		this.clearPosts();
		this.showHideMenuOptions();

		// Set up infinite scrolling.
		var self = this;
		self.currentPostCount = 0;
		var win = $(window);
		var doc = $(document);
		var throttledFn = _.throttle(function () {
			if (win.scrollTop() >= doc.height() - win.height() - 10) {
				if (self.enableInfiniteScrolling && self.articleCollection)
				{
					self.enableInfiniteScrolling = false;
					self.articleCollection.currentPage += 1;
					self.articleCollection.fetch({
						success: function(collection, response, options) {
							if (self.articleCollection.length != self.currentPostCount)
							{
								self.enableInfiniteScrolling = true;
								self.currentPostCount = self.articleCollection.length;
							}
						},
						error: function(x,y,z) { y.url = self.articleCollection.urlBase; self.collectionFetchErrorHandler(x,y,z); },
						reset: false,
						remove: false
					});
				}
   			}
		}, 1000);
		$(window).scroll(throttledFn);

		// Get parameters from local storage, if available.
		if (typeof(Storage) !== "undefined")
		{
			if (!localStorage.showOnlyUnread)
			{
				localStorage.showOnlyUnread = true;
			}
			this.showOnlyUnread = localStorage.showOnlyUnread;

			// Only update HTML if we're showing all, since the HTML is hardcoded as unread only.
			// Note: why is it making things into strings? :(
			if (this.showOnlyUnread == "false")
			{
				$("#showAllPosts").unwrap();
				$("#showUnreadPosts").wrap("<a />");
			}

			// Reset width of feed list from last session.
			var listWidth = $(".leftcol").width;
			if (localStorage.feedListWidth !== "undefined")
			{
				listWidth = 1 * localStorage.feedListWidth; // typecast to int
			}
			$(".leftcol").width(listWidth);
			$(".rightcol").css("margin-left", listWidth);
		}
		else
		{
			// Browser doesn't support local storage, default to unread only.
			this.showOnlyUnread = true;
		}

		this.listenTo(NewsFeeds, 'add', this.addOneFeed);
		this.listenTo(NewsFeeds, 'reset', this.addAllFeeds);
		this.listenTo(NewsFeeds, 'sort', this.addAllFeeds);

		// Perform initial fetch from server.
		NewsFeeds.reset(bootstrappedFeeds);
		self.updateFeedCounts();
		NewsFeeds.each(function(x) {
			self.stopListening(x, 'change:numUnread');
			self.listenTo(x, 'change:numUnread', function() { self.updateFeedCounts(); });
		});

		// Update feed counts every five minutes.
		var self = this;
		window.setInterval(function() { self.updateFeeds(); }, 1000 * (60 * 5));

		// make the feed list resizable.
		this.makeDraggable();
	},

	showImportWindow: function() {
		this.UploadForm = new FileUploadForm();
		this.UploadForm.show();
	},

	// We're not using JQuery's because of the weird way we've set up the CSS.
	makeDraggable: function() {
		var self = this;

		var mouseMoveHandler = function(event) {
			event.preventDefault();
			var newWidth = self._startWidth + (event.pageX - self._startX);
			$(".leftcol").width(newWidth);
			$(".rightcol").css("margin-left", newWidth);

			if (typeof(Storage) !== "undefined")
			{
				localStorage.feedListWidth = newWidth;
			}
		};

		var mouseUpHandler = function(event) {
			$(document).unbind("mousemove", mouseMoveHandler);
			$(document).unbind("mouseup", mouseUpHandler);
		};

		var mouseDownHandler = function(event) {
			event.preventDefault();
			self._startX = event.pageX;
			self._startWidth = $(".leftcol").width();

			$(document).mousemove(mouseMoveHandler);
			$(document).mouseup(mouseUpHandler);
		};

		$(".leftcol").mousedown(mouseDownHandler);
	},

	updateFeeds: function() {
		var self = this;

		NewsFeeds.fetch({
			success: function(collection, response, options) {
				self.updateFeedCounts();
				NewsFeeds.each(function(x) {
					self.stopListening(x, 'change:numUnread');
					self.listenTo(x, 'change:numUnread', function() { self.updateFeedCounts(); });
				});
			},
			error: function(x,y,z) {  y.url = NewsFeeds.urlBase; self.collectionFetchErrorHandler(x,y,z); }
		});
	},

	updateFeedCounts: function() {
		// add up all of the unread posts and put under "All".
		var total_unread = NewsFeeds.reduce(function(memo, feed) { return memo + feed.get("numUnread"); }, 0);
		$("#allCount").text(total_unread);
		if (total_unread == 0) {
			$("#allCount").addClass("hide-element");
		} else {
			$("#allCount").removeClass("hide-element");
		}

		// also update count in the title
		document.title = document.title.replace(/^\(\d+\)\s*/, "");
		if (total_unread > 0)
		{
			document.title = "(" + total_unread + ") " + document.title;
		}
	},

	selectFeed: function(feed) {
		this.enableInfiniteScrolling = false;
		this.clearPosts();

		$(".welcomeblock").addClass("hide-element");
		$("#homeEntry").removeClass("selectedfeed");
		$("#allFeedEntry").removeClass("selectedfeed");
		if (this.selectedFeed) {
			this.selectedFeed.$el.removeClass("selectedfeed");
		}
		this.selectedFeed = feed;

		this.articleCollection = new NewsArticleCollection([]);
		if (feed) {
			this.articleCollection.urlBase = '/feeds/' + feed.model.id + "/posts?unread_only=" + this.showOnlyUnread;
			feed.$el.addClass("selectedfeed");

			// set up correct website URL that the feed provided.
			$("#feedsiteurl").attr("href", feed.model.get("feed").link);
		} else {
			this.articleCollection.urlBase = "/posts?unread_only=" + this.showOnlyUnread;
			this.$("#allFeedEntry").addClass("selectedfeed");
		}

		this.showHideMenuOptions();

		this.listenTo(this.articleCollection, 'add', this.addOneArticle);
		this.listenTo(this.articleCollection, 'reset', this.addAllArticles);

		var self = this;
		this.articleCollection.fetch({
			success: function(collection, response, options) {
				self.enableInfiniteScrolling = true;
				self.currentPostCount = collection.length;
			},
			error: function(x,y,z) { y.url = self.articleCollection.urlBase; self.collectionFetchErrorHandler(x,y,z); }
		});
	},

	sortFeeds: function() {
		this.addAllFeeds();
	},

	markAllRead: function() {
		this.articleCollection.each(function(article) {
			if (article.get("unread")) {
				article.set("unread", false);
			}
		}, this);
	},

	addOneFeed: function(feed) {
		var newView = new NewsFeedView({model: feed});
		var renderedView = newView.render();
		feed.view = newView;
		$("#allfeeds").append(renderedView.el);
		if (this.selectedFeed && this.selectedFeed.model.id == feed.id)
		{
			renderedView.$el.addClass("selectedfeed");
			this.selectedFeed = newView;
		}
	},

	addAllFeeds: function() {
		$("#allfeeds").children().remove();
		NewsFeeds.each(this.addOneFeed, this);
	},

	addOneArticle: function(feed) {
		var newView = new NewsArticleView({model: feed});
		this.$("#postlist").append(newView.render().el);
	},

	addAllArticles: function() {
		this.$("#postlist").children().remove();
		this.articleCollection.each(this.addOneArticle, this);
	},

	clearPosts: function() {
		this.enableInfiniteScrolling = false;

		if (this.articleCollection) {
			// clear event handlers
			this.articleCollection.stopListening();
			this.articleCollection.reset();
			this.articleCollection = null;

			// remove posts
			this.$("#postlist").empty();
			self.currentPostCount = 0;
		}

		// set selected to Home
		$(".selectedfeed").removeClass("selectedfeed");
		$("#allFeedEntry").removeClass("selectedfeed");
		$("#homeEntry").addClass("selectedfeed");
		$(".welcomeblock").removeClass("hide-element");

		this.selectedFeed = null;
		this.showHideMenuOptions();
	},

	showHideMenuOptions: function() {
		//$(".authonly").addClass("hide-element");
		$(".feedonly").addClass("hide-element");
		$(".allonly").addClass("hide-element");

		if (this.selectedFeed) {
			$(".feedonly").removeClass("hide-element");
		} else {
			if ($("#allFeedEntry").hasClass("selectedfeed")) {
				$(".allonly").removeClass("hide-element");
			}
		}
	},

	toggleAllPosts: function() {
		$("#showAllPosts").unwrap();
		$("#showUnreadPosts").wrap("<a />");

		this.showOnlyUnread = false;
		if (typeof(Storage) !== "undefined")
		{
			localStorage.showOnlyUnread = this.showOnlyUnread;
		}
		this.selectFeed(this.selectedFeed);
	},

	toggleUnreadPosts: function() {
		$("#showUnreadPosts").unwrap();
		$("#showAllPosts").wrap("<a />");

		this.showOnlyUnread = true;
		if (typeof(Storage) !== "undefined")
		{
			localStorage.showOnlyUnread = this.showOnlyUnread;
		}
		this.selectFeed(this.selectedFeed);
	},

	addNewFeed: function() {
		// TODO: we probably want something a lot nicer than JS prompt().
		var feedUrl = prompt("Enter the URL of the feed or website that you wish to subscribe to.");

		if (feedUrl) {
			var self = this;
			NewsFeeds.addFeed(feedUrl, function() {
				self.updateFeedCounts();
			});
		}
	},

	addBulkFeeds: function(feeds) {
	    var index = 0;
	    var self = this;
		var feedFn = function() {
			if (index < feeds.length)
			{
				$("#currentImportingFeed").text(index);

				var url = feeds[index];
				var nextFn = function() { index++;  self.updateFeedCounts(); feedFn(); };
				NewsFeeds.addFeed(url, nextFn, nextFn);
			}
			else
			{
				$("#currentImportingFeed").text(index + 1);
				$("#importFeedsLink").show();
				$("#importProgress").addClass("hide-element");

				noty({ text: "Import complete.", layout: "topRight", timeout: 2000, dismissQueue: true, type: "success" });
			}
		};

		if (feeds.length > 0)
		{
			$("#totalImportCount").text(feeds.length);
			$("#importFeedsLink").hide();
			$("#importProgress").removeClass("hide-element");
			feedFn();
		}
	},

	removeFeed: function() {
		// TODO: we probably want something a lot nicer than JS confirm()
		var confirmed = confirm("Are you sure you want to unsubscribe from this feed/website?");
		if (confirmed) {
			var feed = this.selectedFeed;
			feed.$el.remove();
			this.selectFeed(null);
			NewsFeeds.remove(feed.model);
			feed.model.destroy();
			this.updateFeedCounts();
		}
	},

	globalAjaxErrorHandler: function(xhr, status, errorThrown) {
		this.showErrorBasedOnResponse(xhr);
	},

	collectionFetchErrorHandler: function(collection, response, options) {
		// Always GET, but response object does not provide it.
		response.type = "GET";
		this.showErrorBasedOnResponse(response);
	},

	showErrorBasedOnResponse: function(xhr) {
		var errorText = "Communications error with the server. Please try again.";
		var url = xhr.url;
		var type = xhr.type.toUpperCase();

		// Custom errors depending on the situation.
		if (xhr.status == 422) {
			errorText = "Invalid input. Please verify and try again.";
		} else if (xhr.status == 401) {
			// Session expired, force a relogin.
			location.refresh();
		} else if (/\/feeds\/?/.test(url) && type == "POST") {
			errorText = "Could not add feed. Please try again.";
		} else if (/\/feeds\/\d+\/?/.test(url) && type == "DELETE") {
			errorText = "Could not delete feed. Please try again.";
		} else if (/\/feeds\/\d+\/posts\/?/.test(url) && type == "GET") {
			errorText = "Could not retrieve posts. Please try again.";
		} else if (/\/feeds\/\d+\/posts\/\d+\/?/.test(url) && type == "DELETE") {
			errorText = "Could not mark post as read. Please try again.";
		} else if (/\/feeds\/\d+\/posts\/\d+\/?/.test(url) && type == "PUT") {
			errorText = "Could not mark post as read. Please try again.";
		} else if (/\/posts\/?/.test(url) && type == "GET") {
			errorText = "Could not retrieve posts. Please try again.";
		} else if (/\/posts\/\d+\/?/.test(url) && type == "DELETE") {
			errorText = "Could not mark post as read. Please try again.";
		} else if (/\/posts\/\d+\/?/.test(url) && type == "PUT") {
			errorText = "Could not mark post as read. Please try again.";
		}

		noty({ text: errorText, layout: "topRight", timeout: 2000, dismissQueue: true, type: "error" });
	}
});

}).call(this);<|endoftext|>


<commit_before>var restify = require('restify');
var socketio = require('socket.io');
var async = require('async');
var process = require('process');
var machine = require('./machine');
var detection_daemon = require('./detection_daemon')();
var config = require('./config');
var PLATFORM = process.platform;
var log = require('./log').logger('engine');
var db = require('./db');
var macros = require('./macros');
var dashboard = require('./dashboard');
var network = require('./network');
var updater = require('./updater');
var glob = require('glob');

var Engine = function() {
    this.version = null;
    this.time_synced = false;
};

function EngineConfigFirstTime(callback) {
    switch(PLATFORM) {
        case 'darwin':
            glob.glob('/dev/cu.usbmodem*', function(err, files) {
                if(files.length >= 2) {
                    var ports = {
                        'control_port_osx' : files[0],
                        'data_port_osx' : files[1]
                    }
                    config.engine.update(ports, function() {
                        callback();
                    });
                }
            });
        break;

        default:
            callback();
        break;
    }
};

Engine.prototype.setTime = function(obj) {
    if(!this.time_synced) {
        this.time_synced = true;
        var d = new Date(obj.utc);
        log.debug("Setting the time to " + d.toUTCString());
        var t = d.getUTCFullYear() + '-' + d.getUTCMonth() + '-' + d.getUTCDay() + ' ' + d.getUTCHours() + ':' + d.getUTCMinutes() + ':' + d.getUTCSeconds()
	cmd = 'timedatectl set-time ' + t + '; timedatectl';
        util.doshell(cmd, function(stdout) {
            console.log(stdout);
        });
    }
}

Engine.prototype.stop = function(callback) {
    this.machine.disconnect();
    this.machine.setState(this.machine, 'stopped');

    //this.server.close();
    //this.server.io.server.close();
    callback(null);
};

Engine.prototype.start = function(callback) {

    async.series([

        // Configure the engine data directories
       function setup_application(callback) {
            log.info('Checking engine data directory tree...');
            config.createDataDirectories(callback);
        },

        // Load the engine configuration from disk
        function load_engine_config(callback) {
            log.info("Loading engine configuration...");
            config.configureEngine(callback);
        },

        function check_engine_config(callback) {
            if(!config.engine.userConfigLoaded) {
                EngineConfigFirstTime(callback);
            } else {
                callback();
            }
        },

        // Create the version string that will be used to identify the software version
        function get_fabmo_version(callback) {
            log.info("Getting engine version...");
            updater.getFabmoVersionString(function(err, string) {
                if(!err) {
                    log.info("Engine version: " + string);
                    this.version = string;
                } else {
                    log.error(err);
                }
                callback();
            }.bind(this));
        }.bind(this),

        // "Apply" the engine configuration, that is, take the configuration values loaded and actually
        // set up the application based on them.
        function apply_engine_config(callback) {
            log.info("Applying engine configuration...");
            config.engine.apply(callback);
        },

	function setup_network(callback) {
		if(config.engine.get('wifi_manager')) {
			log.info("Setting up the network...");
            try {
                network.init();
            } catch(e) {
                log.error('Problem starting network manager:')
                log.error(e);
            }
			callback(null);
		} else {
			log.warn("Skipping network setup because wifi manager is disabled.");
			callback(null);
		}
	},

        // Configure the DB
        function setup_database(callback) {
            log.info("Configuring database...");
            db.configureDB(callback);
        },

        // Cleanup the DB
        function clean_database(callback) {
            log.info("Cleaning up database...");
            db.cleanup(callback);
        },

        // Connect to G2 and initialize machine runtimes
        function connect(callback) {
            log.info("Connecting to G2...");
            machine.connect(function(err, machine) {
                if(err) {
                    log.error("!!!!!!!!!!!!!!!!!!!!!!!!");
                    log.error("Could not connect to G2.");
                    log.error("(" + err + ")");
                    log.error("!!!!!!!!!!!!!!!!!!!!!!!!");
                }
                callback(null);
            });
        }.bind(this),

        function load_machine_config(callback) {
            this.machine = machine.machine;
            log.info('Loading the machine configuration...')
            config.configureMachine(this.machine.driver, function(err, result) {
                if(err) {
                    log.warn(err);
                }
                callback(null);
            });
        }.bind(this),

	function set_units(callback) {
		this.machine.driver.setUnits(config.machine.get('units'), callback);
	}.bind(this),

        // Configure G2 by loading all its json settings and static configuration parameters
        function load_driver_config(callback) {
            if(this.machine.isConnected()) {
                log.info("Configuring G2...");
                config.configureDriver(machine.machine.driver, function(err, data) {
                    if(err) {
                        log.error("There were problems loading the G2 configuration.");
                    }
                    callback(null);
                });
            } else {
                log.warn("Skipping G2 configuration due to no connection.");
                config.configureDriver(null, function(err, data) {
                    callback(null);
                })
                callback(null);
            }
        }.bind(this),

        function get_g2_version(callback) {
            if(this.machine.isConnected()) {
                log.info("Getting G2 firmware version...");
                this.machine.driver.get('fb', function(err, value) {
                    if(err) {
                        log.error('Could not get the G2 firmware build. (' + err + ')');
                    } else {
                        log.info('G2 Firmware Build: ' + value);
                    }
                    callback(null);
                });
            } else {
                log.warn("Skipping G2 firmware version check due to no connection.")
                callback(null);
            }
    	}.bind(this),

        function apply_machine_config(callback) {
            log.info("Applying machine configuration...");
            config.machine.apply(callback);
        }.bind(this),

        function load_opensbp_commands(callback) {
            log.info("Loading OpenSBP Commands...");
            this.machine.sbp_runtime.loadCommands(callback);
        }.bind(this),

        function load_opensbp_config(callback) {
            log.info("Configuring OpenSBP runtime...");
            config.configureOpenSBP(callback);
        },

        function configure_dashboard(callback) {
            log.info("Configuring dashboard...");
            dashboard.configure(callback);
        },

        function load_apps(callback) {
            log.info("Loading dashboard apps...");
            dashboard.loadApps(function(err, result) {
                callback(null, result);
            });
        },

        function load_macros(callback) {
            log.info("Loading macros...")
            macros.load(callback);
        },

        // Kick off the server if all of the above went OK.
        function start_server(callback) {
            log.info("Setting up the webserver...");
            var server = restify.createServer({name:"FabMo Engine"});
            this.server = server;

            // Allow JSON over Cross-origin resource sharing
            log.info("Configuring cross-origin requests...");
            server.use(
                function crossOrigin(req,res,next){
                    res.header("Access-Control-Allow-Origin", "*");
                    res.header("Access-Control-Allow-Headers", "X-Requested-With");
                    return next();
                }
            );

            server.on('uncaughtException', function(req, res, route, err) {
                log.uncaught(err);
                answer = {
                    status:"error",
                    message:err
                };
                res.json(answer)
            });

            // Configure local directory for uploading files
            log.info("Cofiguring upload directory...");
            server.use(restify.bodyParser({'uploadDir':config.engine.get('upload_dir') || '/tmp'}));
            server.pre(restify.pre.sanitizePath());

            // Import the routes module and apply the routes to the server
            log.info("Loading routes...");
            server.io = socketio.listen(server.server);
            var routes = require('./routes')(server);

            // Kick off the server listening for connections
            server.listen(config.engine.get('server_port'), function() {
                log.info(server.name+ ' listening at '+ server.url);
                callback(null, server);
            });

        }.bind(this),
        ],

        function(err, results) {
            if(err) {
                log.error(err);
                typeof callback === 'function' && callback(err);
            } else {
                typeof callback === 'function' && callback(null, this);
            }
        }.bind(this)
    );
};

module.exports = new Engine();
<commit_msg>Enable gzipping of responses (experimental)<commit_after>var restify = require('restify');
var socketio = require('socket.io');
var async = require('async');
var process = require('process');
var machine = require('./machine');
var detection_daemon = require('./detection_daemon')();
var config = require('./config');
var PLATFORM = process.platform;
var log = require('./log').logger('engine');
var db = require('./db');
var macros = require('./macros');
var dashboard = require('./dashboard');
var network = require('./network');
var updater = require('./updater');
var glob = require('glob');

var Engine = function() {
    this.version = null;
    this.time_synced = false;
};

function EngineConfigFirstTime(callback) {
    switch(PLATFORM) {
        case 'darwin':
            glob.glob('/dev/cu.usbmodem*', function(err, files) {
                if(files.length >= 2) {
                    var ports = {
                        'control_port_osx' : files[0],
                        'data_port_osx' : files[1]
                    }
                    config.engine.update(ports, function() {
                        callback();
                    });
                }
            });
        break;

        default:
            callback();
        break;
    }
};

Engine.prototype.setTime = function(obj) {
    if(!this.time_synced) {
        this.time_synced = true;
        var d = new Date(obj.utc);
        log.debug("Setting the time to " + d.toUTCString());
        var t = d.getUTCFullYear() + '-' + d.getUTCMonth() + '-' + d.getUTCDay() + ' ' + d.getUTCHours() + ':' + d.getUTCMinutes() + ':' + d.getUTCSeconds()
	cmd = 'timedatectl set-time ' + t + '; timedatectl';
        util.doshell(cmd, function(stdout) {
            console.log(stdout);
        });
    }
}

Engine.prototype.stop = function(callback) {
    this.machine.disconnect();
    this.machine.setState(this.machine, 'stopped');

    //this.server.close();
    //this.server.io.server.close();
    callback(null);
};

Engine.prototype.start = function(callback) {

    async.series([

        // Configure the engine data directories
       function setup_application(callback) {
            log.info('Checking engine data directory tree...');
            config.createDataDirectories(callback);
        },

        // Load the engine configuration from disk
        function load_engine_config(callback) {
            log.info("Loading engine configuration...");
            config.configureEngine(callback);
        },

        function check_engine_config(callback) {
            if(!config.engine.userConfigLoaded) {
                EngineConfigFirstTime(callback);
            } else {
                callback();
            }
        },

        // Create the version string that will be used to identify the software version
        function get_fabmo_version(callback) {
            log.info("Getting engine version...");
            updater.getFabmoVersionString(function(err, string) {
                if(!err) {
                    log.info("Engine version: " + string);
                    this.version = string;
                } else {
                    log.error(err);
                }
                callback();
            }.bind(this));
        }.bind(this),

        // "Apply" the engine configuration, that is, take the configuration values loaded and actually
        // set up the application based on them.
        function apply_engine_config(callback) {
            log.info("Applying engine configuration...");
            config.engine.apply(callback);
        },

	function setup_network(callback) {
		if(config.engine.get('wifi_manager')) {
			log.info("Setting up the network...");
            try {
                network.init();
            } catch(e) {
                log.error('Problem starting network manager:')
                log.error(e);
            }
			callback(null);
		} else {
			log.warn("Skipping network setup because wifi manager is disabled.");
			callback(null);
		}
	},

        // Configure the DB
        function setup_database(callback) {
            log.info("Configuring database...");
            db.configureDB(callback);
        },

        // Cleanup the DB
        function clean_database(callback) {
            log.info("Cleaning up database...");
            db.cleanup(callback);
        },

        // Connect to G2 and initialize machine runtimes
        function connect(callback) {
            log.info("Connecting to G2...");
            machine.connect(function(err, machine) {
                if(err) {
                    log.error("!!!!!!!!!!!!!!!!!!!!!!!!");
                    log.error("Could not connect to G2.");
                    log.error("(" + err + ")");
                    log.error("!!!!!!!!!!!!!!!!!!!!!!!!");
                }
                callback(null);
            });
        }.bind(this),

        function load_machine_config(callback) {
            this.machine = machine.machine;
            log.info('Loading the machine configuration...')
            config.configureMachine(this.machine.driver, function(err, result) {
                if(err) {
                    log.warn(err);
                }
                callback(null);
            });
        }.bind(this),

	function set_units(callback) {
		this.machine.driver.setUnits(config.machine.get('units'), callback);
	}.bind(this),

        // Configure G2 by loading all its json settings and static configuration parameters
        function load_driver_config(callback) {
            if(this.machine.isConnected()) {
                log.info("Configuring G2...");
                config.configureDriver(machine.machine.driver, function(err, data) {
                    if(err) {
                        log.error("There were problems loading the G2 configuration.");
                    }
                    callback(null);
                });
            } else {
                log.warn("Skipping G2 configuration due to no connection.");
                config.configureDriver(null, function(err, data) {
                    callback(null);
                })
                callback(null);
            }
        }.bind(this),

        function get_g2_version(callback) {
            if(this.machine.isConnected()) {
                log.info("Getting G2 firmware version...");
                this.machine.driver.get('fb', function(err, value) {
                    if(err) {
                        log.error('Could not get the G2 firmware build. (' + err + ')');
                    } else {
                        log.info('G2 Firmware Build: ' + value);
                    }
                    callback(null);
                });
            } else {
                log.warn("Skipping G2 firmware version check due to no connection.")
                callback(null);
            }
    	}.bind(this),

        function apply_machine_config(callback) {
            log.info("Applying machine configuration...");
            config.machine.apply(callback);
        }.bind(this),

        function load_opensbp_commands(callback) {
            log.info("Loading OpenSBP Commands...");
            this.machine.sbp_runtime.loadCommands(callback);
        }.bind(this),

        function load_opensbp_config(callback) {
            log.info("Configuring OpenSBP runtime...");
            config.configureOpenSBP(callback);
        },

        function configure_dashboard(callback) {
            log.info("Configuring dashboard...");
            dashboard.configure(callback);
        },

        function load_apps(callback) {
            log.info("Loading dashboard apps...");
            dashboard.loadApps(function(err, result) {
                callback(null, result);
            });
        },

        function load_macros(callback) {
            log.info("Loading macros...")
            macros.load(callback);
        },

        // Kick off the server if all of the above went OK.
        function start_server(callback) {
            log.info("Setting up the webserver...");
            var server = restify.createServer({name:"FabMo Engine"});
            this.server = server;

            // Allow JSON over Cross-origin resource sharing
            log.info("Configuring cross-origin requests...");
            server.use(
                function crossOrigin(req,res,next){
                    res.header("Access-Control-Allow-Origin", "*");
                    res.header("Access-Control-Allow-Headers", "X-Requested-With");
                    return next();
                }
            );

            server.on('uncaughtException', function(req, res, route, err) {
                log.uncaught(err);
                answer = {
                    status:"error",
                    message:err
                };
                res.json(answer)
            });

            // Configure local directory for uploading files
            log.info("Cofiguring upload directory...");
            server.use(restify.bodyParser({'uploadDir':config.engine.get('upload_dir') || '/tmp'}));
            server.pre(restify.pre.sanitizePath());

            log.info("Enabling gzip for transport...");
            server.use(restify.gzipResponse());
            // Import the routes module and apply the routes to the server
            log.info("Loading routes...");
            server.io = socketio.listen(server.server);
            var routes = require('./routes')(server);

            // Kick off the server listening for connections
            server.listen(config.engine.get('server_port'), function() {
                log.info(server.name+ ' listening at '+ server.url);
                callback(null, server);
            });

        }.bind(this),
        ],

        function(err, results) {
            if(err) {
                log.error(err);
                typeof callback === 'function' && callback(err);
            } else {
                typeof callback === 'function' && callback(null, this);
            }
        }.bind(this)
    );
};

module.exports = new Engine();
<|endoftext|>


<commit_before>var assert = require('assert');
var five = require('./');

assert.equal(5, five(), 'Five should give you five');
assert.notEqual(6, five(), 'Five should not give you not five');

assert.equal(five.convertTo(5), 5);
assert.equal(five.convertTo(3), 3);
assert.equal(five.convertTo(665456), 665456);

assert.equal('⁵', five.upHigh(), 'An up high five should be a superscripted 5');
assert.equal('₅', five.downLow(), 'A down low five should be a subscripted 5');
assert.equal('V', five.roman(), 'A roman five should be a V');

assert.equal('خمسة', five.arabic(), 'A arabic five should be خمسة');
assert.equal('beş', five.azerbaijani(), 'A azerbaijani five should be beş');
assert.equal('bost', five.basque(), 'A basque five should be bost');
assert.equal('пяць', five.belarusian(), 'A belarusian five should be пяць');
assert.equal('pet', five.bosnian(), 'A bosnian five should be pet');
assert.equal('пет', five.bulgarian(), 'A bulgarian five should be пет');
assert.equal('cinc', five.catalan(), 'A catalan five should be cinc');
assert.equal('五', five.chinese(), 'A chinese five should be 五');
assert.equal('tahlapi', five.choctaw(), 'A choctaw five should be tahlapi');
assert.equal('pet', five.croatian(), 'A croatian five should be pet');
assert.equal('pět', five.czech(), 'A czech five should be pět');
assert.equal('hen', five.dovah(), 'A dovah five should be hen');
assert.equal('vijf', five.dutch(), 'A dutch five should be vijf');
assert.equal('lempe', five.elvish(), 'A elvish five should be lempe');
assert.equal('five', five.english(), 'A english five should be five');
assert.equal('viis', five.estonian(), 'An estonian five should be viis');
assert.equal('viisi', five.finnish(), 'A finnish five should be viisi');
assert.equal('cinq', five.french(), 'A french five should be cinq');
assert.equal('fünf', five.german(), 'A german five should be fünf');
assert.equal('πέντε', five.greek(), 'A greek five should be πέντε');
assert.equal('חמש', five.hebrew(), 'A hebrew five should be חמש');
assert.equal('पांच', five.hindi(), 'A hindi five should be पांच');
assert.equal('öt', five.hungarian(), 'A hungarian five should be öt');
assert.equal('lima', five.indonesian(), 'A indonesian five should be lima');
assert.equal('cúig', five.irish(), 'A irish five should be cúig');
assert.equal('cinque', five.italian(), 'A italian five should be cinque');
assert.equal('五', five.japanese(), 'A japanese five should be 五');
assert.equal('ಐದು', five.kannada(), 'A kannada five should be ಐದು');
assert.equal('vagh', five.klingon(), 'A klingon five should be vagh');
assert.equal('오', five.korean(), 'A korean five should be 오');
assert.equal('quinque', five.latin(), 'A latin five should be quinque');
assert.equal('pieci', five.latvian(), 'A latvian five should be pieci');
assert.equal('penki', five.lithuanian(), 'A lithuanian five should be penki');
assert.equal('таван', five.mongolian(), 'A mongolian five should be таван');
assert.equal('fem', five.norwegian(), 'A norwegian five should be fem');
assert.equal('پنج', five.persian(), 'A persian five should be پنج');
assert.equal('ivefay', five.piglatin(), 'A piglatin five should be ivefay');
assert.equal('pięć', five.polish(), 'A polish five should be pięć');
assert.equal('cinco', five.portuguese(), 'A portuguese five should be cinco');
assert.equal('cinci', five.romanian(), 'A romanian five should be cinci');
assert.equal('пять', five.russian(), 'A russian five should be пять');
assert.equal('pet', five.serbian(), 'A serbian five should be pet');
assert.equal('päť', five.slovakian(), 'A slovakian five should be päť');
assert.equal('pet', five.slovenian(), 'A slovenian five should be pet');
assert.equal('cinco', five.spanish(), 'A spanish five should be cinco');
assert.equal('fem', five.swedish(), 'A swedish five should be fem');
assert.equal('ஐந்து', five.tamil(), 'A tamil five should be ஐந்து');
assert.equal('ఐదు', five.telugu(), 'A telugu five should be ఐదు');
assert.equal('ห้า', five.thai(), 'A thai five should be ห้า');
assert.equal('beş', five.turkish(), 'A turkish five should be beş');
assert.equal('п’ять', five.ukrainian(), 'A ukrainian five should be п’ять');

assert.equal('....-', five.morseCode(), 'A five in morse code should be ....-');
assert.equal('101', five.binary(), 'A binary five should be 101');
assert.equal('5', five.octal(), 'An octal five should be 5');
assert.equal('5', five.hex(), 'An hexadecimal five should be 5');

assert.equal('-5', five.negative(), 'A negative five should be -5');
assert.equal('FIVE', five.loud(), 'A loud five should be FIVE');
assert.equal('IVEFAY', five.loud('piglatin'), 'A loud five in Pig Latin should be IVEFAY');
assert.equal('ПЯТЬ', five.loud('russian'), 'A loud five in Russian should be ПЯТЬ');
assert.equal('S', five.smooth(), 'A smooth five should be S');

assert.equal('🕔', five.oclock(), 'A unicode symbol for five o\'clock should be U+1F554');

var now = new Date().valueOf();
var slowFive = five.tooSlow();
var finishes = new Date().valueOf();

assert.equal(5, slowFive, 'A too slow five should still be five');
assert.ok((finishes - now) > 500, 'A too slow five should take longer than 500 milliseconds to be returned, blocking execution and generally being a bad idea');

assert.equal(JSON.stringify(['Jackie','Tito','Jermaine','Marlon','Michael']), JSON.stringify(five.jackson()), 'A Jackson five should be the five members of the 70\'s pop family group the Jackson 5');

assert.equal(JSON.stringify(['Juwan Howard','Ray Jackson','Jimmy King','Jalen Rose','Chris Webber']), JSON.stringify(five.fab()), 'A fab five should be the 1991-1993 Michigan Mens Basketball Team');

assert.equal(JSON.stringify([5, 5, 5]), JSON.stringify(five.map([1, 2, 3])));
assert.equal(5, five.reduce([1, 2, 3]));

assert.equal('5678901234', five.rot('0123456789'), 'Numbers should be rotated');
assert.equal('fghijklmnopqrstuvwxyzabcde', five.rot('abcdefghijklmnopqrstuvwxyz'), 'Small letters should be rotated');
assert.equal('FGHIJKLMNOPQRSTUVWXYZABCDE', five.rot('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'Capital letters too');
assert.equal('$_$ -,- @.@?', five.rot('$_$ -,- @.@?'), 'Emoticons should not be rotated');

assert.equal(five * five, 25);
assert.equal(five + five, 10);
assert.equal(five / five, 1);
assert.equal(five - five, 0);
assert.equal((five / five) * (five), five);

var asyncTests = 1;
five.async(function(err, five) {
	assert.equal(err, null, 'Async should not result in an error.');
	assert.equal(five, 5, 'Async should pass 5 to callback.');
	asyncTests--;
});

setInterval(function() {
	if (!asyncTests) {
		process.exit(0);
	}
}, 100);<commit_msg>Fix test for Morse code<commit_after>var assert = require('assert');
var five = require('./');

assert.equal(5, five(), 'Five should give you five');
assert.notEqual(6, five(), 'Five should not give you not five');

assert.equal(five.convertTo(5), 5);
assert.equal(five.convertTo(3), 3);
assert.equal(five.convertTo(665456), 665456);

assert.equal('⁵', five.upHigh(), 'An up high five should be a superscripted 5');
assert.equal('₅', five.downLow(), 'A down low five should be a subscripted 5');
assert.equal('V', five.roman(), 'A roman five should be a V');

assert.equal('خمسة', five.arabic(), 'A arabic five should be خمسة');
assert.equal('beş', five.azerbaijani(), 'A azerbaijani five should be beş');
assert.equal('bost', five.basque(), 'A basque five should be bost');
assert.equal('пяць', five.belarusian(), 'A belarusian five should be пяць');
assert.equal('pet', five.bosnian(), 'A bosnian five should be pet');
assert.equal('пет', five.bulgarian(), 'A bulgarian five should be пет');
assert.equal('cinc', five.catalan(), 'A catalan five should be cinc');
assert.equal('五', five.chinese(), 'A chinese five should be 五');
assert.equal('tahlapi', five.choctaw(), 'A choctaw five should be tahlapi');
assert.equal('pet', five.croatian(), 'A croatian five should be pet');
assert.equal('pět', five.czech(), 'A czech five should be pět');
assert.equal('hen', five.dovah(), 'A dovah five should be hen');
assert.equal('vijf', five.dutch(), 'A dutch five should be vijf');
assert.equal('lempe', five.elvish(), 'A elvish five should be lempe');
assert.equal('five', five.english(), 'A english five should be five');
assert.equal('viis', five.estonian(), 'An estonian five should be viis');
assert.equal('viisi', five.finnish(), 'A finnish five should be viisi');
assert.equal('cinq', five.french(), 'A french five should be cinq');
assert.equal('fünf', five.german(), 'A german five should be fünf');
assert.equal('πέντε', five.greek(), 'A greek five should be πέντε');
assert.equal('חמש', five.hebrew(), 'A hebrew five should be חמש');
assert.equal('पांच', five.hindi(), 'A hindi five should be पांच');
assert.equal('öt', five.hungarian(), 'A hungarian five should be öt');
assert.equal('lima', five.indonesian(), 'A indonesian five should be lima');
assert.equal('cúig', five.irish(), 'A irish five should be cúig');
assert.equal('cinque', five.italian(), 'A italian five should be cinque');
assert.equal('五', five.japanese(), 'A japanese five should be 五');
assert.equal('ಐದು', five.kannada(), 'A kannada five should be ಐದು');
assert.equal('vagh', five.klingon(), 'A klingon five should be vagh');
assert.equal('오', five.korean(), 'A korean five should be 오');
assert.equal('quinque', five.latin(), 'A latin five should be quinque');
assert.equal('pieci', five.latvian(), 'A latvian five should be pieci');
assert.equal('penki', five.lithuanian(), 'A lithuanian five should be penki');
assert.equal('таван', five.mongolian(), 'A mongolian five should be таван');
assert.equal('fem', five.norwegian(), 'A norwegian five should be fem');
assert.equal('پنج', five.persian(), 'A persian five should be پنج');
assert.equal('ivefay', five.piglatin(), 'A piglatin five should be ivefay');
assert.equal('pięć', five.polish(), 'A polish five should be pięć');
assert.equal('cinco', five.portuguese(), 'A portuguese five should be cinco');
assert.equal('cinci', five.romanian(), 'A romanian five should be cinci');
assert.equal('пять', five.russian(), 'A russian five should be пять');
assert.equal('pet', five.serbian(), 'A serbian five should be pet');
assert.equal('päť', five.slovakian(), 'A slovakian five should be päť');
assert.equal('pet', five.slovenian(), 'A slovenian five should be pet');
assert.equal('cinco', five.spanish(), 'A spanish five should be cinco');
assert.equal('fem', five.swedish(), 'A swedish five should be fem');
assert.equal('ஐந்து', five.tamil(), 'A tamil five should be ஐந்து');
assert.equal('ఐదు', five.telugu(), 'A telugu five should be ఐదు');
assert.equal('ห้า', five.thai(), 'A thai five should be ห้า');
assert.equal('beş', five.turkish(), 'A turkish five should be beş');
assert.equal('п’ять', five.ukrainian(), 'A ukrainian five should be п’ять');

assert.equal('.....', five.morseCode(), 'A five in morse code should be .....');
assert.equal('101', five.binary(), 'A binary five should be 101');
assert.equal('5', five.octal(), 'An octal five should be 5');
assert.equal('5', five.hex(), 'An hexadecimal five should be 5');

assert.equal('-5', five.negative(), 'A negative five should be -5');
assert.equal('FIVE', five.loud(), 'A loud five should be FIVE');
assert.equal('IVEFAY', five.loud('piglatin'), 'A loud five in Pig Latin should be IVEFAY');
assert.equal('ПЯТЬ', five.loud('russian'), 'A loud five in Russian should be ПЯТЬ');
assert.equal('S', five.smooth(), 'A smooth five should be S');

assert.equal('🕔', five.oclock(), 'A unicode symbol for five o\'clock should be U+1F554');

var now = new Date().valueOf();
var slowFive = five.tooSlow();
var finishes = new Date().valueOf();

assert.equal(5, slowFive, 'A too slow five should still be five');
assert.ok((finishes - now) > 500, 'A too slow five should take longer than 500 milliseconds to be returned, blocking execution and generally being a bad idea');

assert.equal(JSON.stringify(['Jackie','Tito','Jermaine','Marlon','Michael']), JSON.stringify(five.jackson()), 'A Jackson five should be the five members of the 70\'s pop family group the Jackson 5');

assert.equal(JSON.stringify(['Juwan Howard','Ray Jackson','Jimmy King','Jalen Rose','Chris Webber']), JSON.stringify(five.fab()), 'A fab five should be the 1991-1993 Michigan Mens Basketball Team');

assert.equal(JSON.stringify([5, 5, 5]), JSON.stringify(five.map([1, 2, 3])));
assert.equal(5, five.reduce([1, 2, 3]));

assert.equal('5678901234', five.rot('0123456789'), 'Numbers should be rotated');
assert.equal('fghijklmnopqrstuvwxyzabcde', five.rot('abcdefghijklmnopqrstuvwxyz'), 'Small letters should be rotated');
assert.equal('FGHIJKLMNOPQRSTUVWXYZABCDE', five.rot('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'Capital letters too');
assert.equal('$_$ -,- @.@?', five.rot('$_$ -,- @.@?'), 'Emoticons should not be rotated');

assert.equal(five * five, 25);
assert.equal(five + five, 10);
assert.equal(five / five, 1);
assert.equal(five - five, 0);
assert.equal((five / five) * (five), five);

var asyncTests = 1;
five.async(function(err, five) {
	assert.equal(err, null, 'Async should not result in an error.');
	assert.equal(five, 5, 'Async should pass 5 to callback.');
	asyncTests--;
});

setInterval(function() {
	if (!asyncTests) {
		process.exit(0);
	}
}, 100);
<|endoftext|>


<commit_before>'use strict';

var udhr,
    writeFile,
    json,
    information,
    getTrigrams,
    highestTrigramCount,
    highestTrigram,
    highestTrigramLanguage,
    topTrigramCount,
    trigramCount,
    allIndex,
    minIndex,
    topIndex;

udhr = require('udhr');
writeFile = require('fs').writeFileSync;
json = udhr.json();
information = udhr.information();
getTrigrams = require('n-gram').trigram;

function all(object, key) {
    var results = [],
        property,
        value;

    for (property in object) {
        value = object[property];

        if (property === key) {
            results.push(value);
        } else if (typeof value === 'object') {
            results = results.concat(all(value, key));
        }
    }

    return results;
}

function clean(value) {
    return value
        .replace(/[\u0021-\u0040]+/g, '')
        .replace(/\s+/g, ' ')
        .toLowerCase();
}

/**
 * Deep regular sort on the number at `1` in both objects. E.g. [1, 5, 20];
 *
 * @param {Array} a
 * @param {Array} b
 * @api private
 */
function sort(a, b) {
    return a[1] - b[1];
}

/**
 * Get an object with trigrams as its attributes, and their occurence count
 * as their values
 *
 * @param {string} value
 * @return {Object.<string, number>} - Object containing weighted trigrams.
 * @api private
 */

function getObjectModel(value) {
    var trigrams = getTrigrams(' ' + value + ' '),
        objectModel = {},
        iterator = -1,
        length = trigrams.length,
        trigram;

    while (++iterator < length) {
        trigram = trigrams[iterator];

        if (trigram in objectModel) {
            objectModel[trigram]++;
        } else {
            objectModel[trigram] = 1;
        }
    }

    return objectModel;
}

/**
 * Get the array containing trigram--count tuples from a given value.
 *
 * @param {string} value
 * @return {Array<string, number>[]} - An array containing trigram--count
 *     tupples.
 * @api private
 */
function getCountedTrigrams(value) {
    var objectModel = getObjectModel(value),
        countedTrigrams = [],
        trigram;

    for (trigram in objectModel) {
        countedTrigrams.push([trigram, objectModel[trigram]]);
    }

    return countedTrigrams;
}

function tuplesToObject(tuples) {
    var object = {};

    tuples.forEach(function (tuple) {
        object[tuple[0]] = tuple[1];
    });

    return object;
}

highestTrigramCount = 0;
topTrigramCount = 0;
trigramCount = 0;

function createIndexFile(type) {
    var queue = [];

    function addFile(code, path) {
        queue.push({
            'code' : code,
            'path' : path
        });
    }

    function done() {
        var lines;

        lines = queue.map(function (file) {
            return '\'' + file.code +
                '\': require(\'./' + type + '/' + file.path + '\')';
        });

        return '\'use strict\';\n' +
            '\n' +
            'module.exports = {\n' +
            '  ' +  lines.join(',\n  ') + '\n' +
            '}\n';
    }

    return {
        'toString' : done,
        'add' : addFile
    };
}

allIndex = createIndexFile('all');
minIndex = createIndexFile('min');
topIndex = createIndexFile('top');

Object.keys(json).forEach(function (code) {
    var plain,
        trigrams,
        topTrigrams,
        trigram,
        language;

    plain = clean(all(json[code], 'para').join(''));
    trigrams = getCountedTrigrams(plain).sort(sort);
    topTrigrams = trigrams.slice(trigrams.length - 300);
    trigram = trigrams[trigrams.length - 1];
    language = information[code].name;

    if (information[code].namedVersion) {
        language += ' ' + information[code].namedVersion;
    }

    console.log(
        'Writing trigram file for: ' + language + '\n' +
        '- Code:                  "' + code + '";\n' +
        '- Highest trigram:       "' + trigram[0] + '";\n' +
        '- Highest trigram count:  ' + trigram[1] + ';\n' +
        '- Total trigrams:         ' + trigrams.length + ';\n' +
        '- Cleaned string length:  ' + plain.length + ';'
    );

    if (trigram[1] > highestTrigramCount) {
        highestTrigramCount = trigram[1];
        highestTrigram = trigram[0];
        highestTrigramLanguage = language;
    }

    trigramCount++;
    allIndex.add(code, code + '.json');

    writeFile('./data/all/' + code + '.json', JSON.stringify(
        tuplesToObject(trigrams), 0, 2
    ));

    if (topTrigrams.length === 300) {
        topTrigramCount++;

        writeFile('./data/top/' + code + '.json', JSON.stringify(
            tuplesToObject(topTrigrams), 0, 2
        ));

        writeFile('./data/min/' + code + '.json', JSON.stringify(
            topTrigrams.map(function (trigram) {
                return trigram[0];
            }), 0, 2
        ));

        topIndex.add(code, code + '.json');
        minIndex.add(code, code + '.json');

        console.log(
            '- Top & min trigram file: yes.'
        );
    } else {
        console.log(
            '- Top & min trigram file: no.'
        );
    }

    console.log('');
});

console.log(
    'The highest trigram was "' + highestTrigram + '" which occurred ' +
    highestTrigramCount + ' times in ' + highestTrigramLanguage + '.\n'
);

writeFile('./data/all.js', allIndex);

console.log(
    'Finished writing ' + trigramCount + ' files.\n'
);

writeFile('./data/top.js', topIndex);

console.log(
    'Finished writing ' + topTrigramCount + ' top files ' +
    '(ignoring ' + (trigramCount - topTrigramCount) + ').\n'
);

writeFile('./data/min.js', minIndex);

console.log(
    'Finished writing ' + topTrigramCount + ' min files ' +
    '(ignoring ' + (trigramCount - topTrigramCount) + ').\n'
);
<commit_msg>Add description to symbols which are removed while cleaning<commit_after>'use strict';

var udhr,
    writeFile,
    json,
    information,
    getTrigrams,
    EXPRESSION_SYMBOLS,
    highestTrigramCount,
    highestTrigram,
    highestTrigramLanguage,
    topTrigramCount,
    trigramCount,
    allIndex,
    minIndex,
    topIndex;

udhr = require('udhr');
writeFile = require('fs').writeFileSync;
json = udhr.json();
information = udhr.information();
getTrigrams = require('n-gram').trigram;

/**
 * - 'EXCLAMATION MARK' (U+0021)
 * - 'QUOTATION MARK' (U+0022)
 * - 'NUMBER SIGN' (U+0023)
 * - 'DOLLAR SIGN' (U+0024)
 * - 'PERCENT SIGN' (U+0025)
 * - 'AMPERSAND' (U+0026)
 * - 'APOSTROPHE' (U+0027)
 * - 'LEFT PARENTHESIS' (U+0028)
 * - 'RIGHT PARENTHESIS' (U+0029)
 * - 'ASTERISK' (U+002A)
 * - 'PLUS SIGN' (U+002B)
 * - 'COMMA' (U+002C)
 * - 'HYPHEN-MINUS' (U+002D)
 * - 'FULL STOP' (U+002E)
 * - 'SOLIDUS' (U+002F)
 * - 'DIGIT ZERO' (U+0030)
 * - 'DIGIT ONE' (U+0031)
 * - 'DIGIT TWO' (U+0032)
 * - 'DIGIT THREE' (U+0033)
 * - 'DIGIT FOUR' (U+0034)
 * - 'DIGIT FIVE' (U+0035)
 * - 'DIGIT SIX' (U+0036)
 * - 'DIGIT SEVEN' (U+0037)
 * - 'DIGIT EIGHT' (U+0038)
 * - 'DIGIT NINE' (U+0039)
 * - 'COLON' (U+003A)
 * - 'SEMICOLON' (U+003B)
 * - 'LESS-THAN SIGN' (U+003C)
 * - 'EQUALS SIGN' (U+003D)
 * - 'GREATER-THAN SIGN' (U+003E)
 * - 'QUESTION MARK' (U+003F)
 * - 'COMMERCIAL AT' (U+0040)
 */

EXPRESSION_SYMBOLS = /[\u0021-\u0040]+/g

function all(object, key) {
    var results = [],
        property,
        value;

    for (property in object) {
        value = object[property];

        if (property === key) {
            results.push(value);
        } else if (typeof value === 'object') {
            results = results.concat(all(value, key));
        }
    }

    return results;
}

function clean(value) {
    return value
        .replace(EXPRESSION_SYMBOLS, '')
        .replace(/\s+/g, ' ')
        .toLowerCase();
}

/**
 * Deep regular sort on the number at `1` in both objects. E.g. [1, 5, 20];
 *
 * @param {Array} a
 * @param {Array} b
 * @api private
 */
function sort(a, b) {
    return a[1] - b[1];
}

/**
 * Get an object with trigrams as its attributes, and their occurence count
 * as their values
 *
 * @param {string} value
 * @return {Object.<string, number>} - Object containing weighted trigrams.
 * @api private
 */

function getObjectModel(value) {
    var trigrams = getTrigrams(' ' + value + ' '),
        objectModel = {},
        iterator = -1,
        length = trigrams.length,
        trigram;

    while (++iterator < length) {
        trigram = trigrams[iterator];

        if (trigram in objectModel) {
            objectModel[trigram]++;
        } else {
            objectModel[trigram] = 1;
        }
    }

    return objectModel;
}

/**
 * Get the array containing trigram--count tuples from a given value.
 *
 * @param {string} value
 * @return {Array<string, number>[]} - An array containing trigram--count
 *     tupples.
 * @api private
 */
function getCountedTrigrams(value) {
    var objectModel = getObjectModel(value),
        countedTrigrams = [],
        trigram;

    for (trigram in objectModel) {
        countedTrigrams.push([trigram, objectModel[trigram]]);
    }

    return countedTrigrams;
}

function tuplesToObject(tuples) {
    var object = {};

    tuples.forEach(function (tuple) {
        object[tuple[0]] = tuple[1];
    });

    return object;
}

highestTrigramCount = 0;
topTrigramCount = 0;
trigramCount = 0;

function createIndexFile(type) {
    var queue = [];

    function addFile(code, path) {
        queue.push({
            'code' : code,
            'path' : path
        });
    }

    function done() {
        var lines;

        lines = queue.map(function (file) {
            return '\'' + file.code +
                '\': require(\'./' + type + '/' + file.path + '\')';
        });

        return '\'use strict\';\n' +
            '\n' +
            'module.exports = {\n' +
            '  ' +  lines.join(',\n  ') + '\n' +
            '}\n';
    }

    return {
        'toString' : done,
        'add' : addFile
    };
}

allIndex = createIndexFile('all');
minIndex = createIndexFile('min');
topIndex = createIndexFile('top');

Object.keys(json).forEach(function (code) {
    var plain,
        trigrams,
        topTrigrams,
        trigram,
        language;

    plain = clean(all(json[code], 'para').join(''));
    trigrams = getCountedTrigrams(plain).sort(sort);
    topTrigrams = trigrams.slice(trigrams.length - 300);
    trigram = trigrams[trigrams.length - 1];
    language = information[code].name;

    if (information[code].namedVersion) {
        language += ' ' + information[code].namedVersion;
    }

    console.log(
        'Writing trigram file for: ' + language + '\n' +
        '- Code:                  "' + code + '";\n' +
        '- Highest trigram:       "' + trigram[0] + '";\n' +
        '- Highest trigram count:  ' + trigram[1] + ';\n' +
        '- Total trigrams:         ' + trigrams.length + ';\n' +
        '- Cleaned string length:  ' + plain.length + ';'
    );

    if (trigram[1] > highestTrigramCount) {
        highestTrigramCount = trigram[1];
        highestTrigram = trigram[0];
        highestTrigramLanguage = language;
    }

    trigramCount++;
    allIndex.add(code, code + '.json');

    writeFile('./data/all/' + code + '.json', JSON.stringify(
        tuplesToObject(trigrams), 0, 2
    ));

    if (topTrigrams.length === 300) {
        topTrigramCount++;

        writeFile('./data/top/' + code + '.json', JSON.stringify(
            tuplesToObject(topTrigrams), 0, 2
        ));

        writeFile('./data/min/' + code + '.json', JSON.stringify(
            topTrigrams.map(function (trigram) {
                return trigram[0];
            }), 0, 2
        ));

        topIndex.add(code, code + '.json');
        minIndex.add(code, code + '.json');

        console.log(
            '- Top & min trigram file: yes.'
        );
    } else {
        console.log(
            '- Top & min trigram file: no.'
        );
    }

    console.log('');
});

console.log(
    'The highest trigram was "' + highestTrigram + '" which occurred ' +
    highestTrigramCount + ' times in ' + highestTrigramLanguage + '.\n'
);

writeFile('./data/all.js', allIndex);

console.log(
    'Finished writing ' + trigramCount + ' files.\n'
);

writeFile('./data/top.js', topIndex);

console.log(
    'Finished writing ' + topTrigramCount + ' top files ' +
    '(ignoring ' + (trigramCount - topTrigramCount) + ').\n'
);

writeFile('./data/min.js', minIndex);

console.log(
    'Finished writing ' + topTrigramCount + ' min files ' +
    '(ignoring ' + (trigramCount - topTrigramCount) + ').\n'
);
<|endoftext|>