<commit_before>/*
 * micro-markdown.js
 * markdown in under 5kb
 *
 * Copyright 2014, Simon Waldherr - http://simon.waldherr.eu/
 * Released under the MIT Licence
 * http://simon.waldherr.eu/license/mit/
 *
 * Github:  https://github.com/simonwaldherr/micromarkdown.js/
 * Version: 0.3.0
 */

var micromarkdown = {
	regexobject: {
		headline: /^(\#{1,6})([^\#\n]+)$/m,
		code: /\s\`\`\`\n?([^`]+)\`\`\`/g,
		hr: /^(?:([\*\-_] ?)+)\1\1$/gm,
		lists: /^((\s*((\*|\-)|\d(\.|\))) [^\n]+)\n)+/gm,
		bolditalic: /(?:([\*_~]{1,3}))([^\*_~\n]+[^\*_~\s])\1/g,
		links: /!?\[([^\]<>]+)\]\(([^ \)<>]+)( "[^\(\)\"]+")?\)/g,
		reflinks: /\[([^\]]+)\]\[([^\]]+)\]/g,
		smlinks: /\@([a-z0-9]{3,})\@(t|gh|fb|gp|adn)/gi,
		mail: /<(([a-z0-9_\-\.])+\@([a-z0-9_\-\.])+\.([a-z]{2,7}))>/gmi,
		tables: /\n(([^|\n]+ *\| *)+([^|\n]+\n))((:?\-+:?\|)+(:?\-+:?)*\n)((([^|\n]+ *\| *)+([^|\n]+)\n)+)/g,
		include: /[\[<]include (\S+) from (https?:\/\/[a-z0-9\.\-]+\.[a-z]{2,9}[a-z0-9\.\-\?\&\/]+)[\]>]/gi,
		url: /<([a-zA-Z0-9@:%_\+.~#?&\/\/=]{2,256}\.[a-z]{2,4}\b(\/[\-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)?)>/g
	},

	codeFilter: function (str, stra) {
		return str.replace(stra[0], '\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '
').replace(/\ /gm, ' ') + '\n');
	},

	headlinesFilter: function (stra, str) {
		var count = stra[1].length;
		return str.replace(stra[0], '<h' + count + '>' + stra[2] + '</h' + count + '>' + '\n');
	},

	linksFilter: function (stra, str, strict) {
		if (stra[0].substr(0, 1) === '!') {
			str = str.replace(stra[0], '\n');
		} else {
			str = str.replace(stra[0], '' + stra[1] + '\n');
		}
		return str;
	},
	mailFilter: function (str, stra) {
		return str.replace(stra[0], '' + stra[1] + '');
	},
	horizontalLineFilter: function (str, stra) {
		str = str.replace(stra[0], '\n
\n');
		return str;
	},
	kinksFilter: function (stra, str, strict) {
		var repString = stra[1];
		if (repString.indexOf('://') === -1) {
			repString = 'http://' + repString;
		}
		str = str.replace(stra[0], '' + repString.replace(/(https:\/\/|http:\/\/|mailto:|ftp:\/\/)/gmi, '') + '');
		return str;
	},
	refLinksFilter: function (str, stra, helper, strict) {
		return str.replace(stra[0], '' + stra[1] + '');
	},
	specialLinks: function (stra, str, strict) {
		var repstr = "";
		switch (stra[2]) {
			case 't':
				repstr = 'https://twitter.com/' + stra[1];
				break;
			case 'gh':
				repstr = 'https://github.com/' + stra[1];
				break;
			case 'fb':
				repstr = 'https://www.facebook.com/' + stra[1];
				break;
			case 'gp':
				repstr = 'https://plus.google.com/+' + stra[1];
				break;
		}
		str = str.replace(stra[0], '' + stra[1] + '');
		return str;
	},
	boldItalicFilter: function (stra, str) {
		var repstr = [];
		if (stra[1] === '~~') {
			str = str.replace(stra[0], '' + stra[2] + '');
		} else {
			switch (stra[1].length) {
				case 1:
					repstr = ['', ''];
					break;
				case 2:
					repstr = ['', ''];
					break;
				case 3:
					repstr = ['', ''];
					break;
			}
			str = str.replace(stra[0], repstr[0] + stra[2] + repstr[1]);
		}
		return str;
	},
	parse: function (str, strict) {
		var line, nstatus = 0,
			status, cel, calign, indent, helper, helper1, helper2, repstr, stra, trashgc = [],
			casca = 0,
			i = 0,
			j = 0;
		str = '\n' + str + '\n';

		var regexobject = micromarkdown.regexobject;
		if (strict !== true) {
			regexobject.lists = /^((\s*(\*|\d\.) [^\n]+)\n)+/gm;
		}

		/* code */
		while ((stra = regexobject.code.exec(str)) !== null) {
			str = this.codeFilter(str, stra);
		}

		/* headlines */
		while ((stra = regexobject.headline.exec(str)) !== null) {
			str = this.headlinesFilter(stra, str);
		}

		/* lists */
		while ((stra = regexobject.lists.exec(str)) !== null) {
			casca = 0;
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr = '
';
			} else {
				repstr = '
';
			}
			helper = stra[0].split('\n');
			helper1 = [];
			status = 0;
			indent = false;
			for (i = 0; i < helper.length; i++) {
				if ((line = /^((\s*)((\*|\-)|\d(\.|\))) ([^\n]+))/.exec(helper[i])) !== null) {
					if ((line[2] === undefined) || (line[2].length === 0)) {
						nstatus = 0;
					} else {
						if (indent === false) {
							indent = line[2].replace(/\t/, '    ').length;
						}
						nstatus = Math.round(line[2].replace(/\t/, '    ').length / indent);
					}
					while (status > nstatus) {
						repstr += helper1.pop();
						status--;
						casca--;
					}
					while (status < nstatus) {
						if ((line[0].trim().substr(0, 1) === '*') || (line[0].trim().substr(0, 1) === '-')) {
							repstr += '
';
							helper1.push('
');
						} else {
							repstr += '
';
							helper1.push('
');
						}
						status++;
						casca++;
					}
					repstr += '
' + line[6] + '
' + '\n';
				}
			}
			while (casca > 0) {
				repstr += '
';
				casca--;
			}
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr += '
';
			} else {
				repstr += '
';
			}
			str = str.replace(stra[0], repstr + '\n');
		}

		/* tables */
		while ((stra = regexobject.tables.exec(str)) !== null) {
			repstr = '

';
			helper = stra[1].split('|');
			calign = stra[4].split('|');
			for (i = 0; i < helper.length; i++) {
				if (calign.length <= i) {
					calign.push(0);
				} else if ((calign[i].trimRight().slice(-1) === ':') && (strict !== true)) {
					if (calign[i][0] === ':') {
						calign[i] = 3;
					} else {
						calign[i] = 2;
					}
				} else if (strict !== true) {
					if (calign[i][0] === ':') {
						calign[i] = 1;
					} else {
						calign[i] = 0;
					}
				} else {
					calign[i] = 0;
				}
			}
			cel = ['', '', '', ''];
			for (i = 0; i < helper.length; i++) {
				repstr += cel[calign[i]] + helper[i].trim() + '	';
			}
			repstr += '
';
			cel = ['', '', '', ''];
			helper1 = stra[7].split('\n');
			for (i = 0; i < helper1.length; i++) {
				helper2 = helper1[i].split('|');
				if (helper2[0].length !== 0) {
					while (calign.length < helper2.length) {
						calign.push(0);
					}
					repstr += '
';
					for (j = 0; j < helper2.length; j++) {
						repstr += cel[calign[j]] + helper2[j].trim() + '	';
					}
					repstr += '
' + '\n';
				}
			}
			repstr += '
';
			str = str.replace(stra[0], repstr);
		}

		/* bold and italic */
		for (i = 0; i < 3; i++) {
			while ((stra = regexobject.bolditalic.exec(str)) !== null) {
				str = this.boldItalicFilter(stra, str);
			}
		}

		/* links */
		while ((stra = regexobject.links.exec(str)) !== null) {
			str = this.linksFilter(stra, str, strict);
		}
		while ((stra = regexobject.mail.exec(str)) !== null) {
			str = this.mailFilter(str, stra);
		}
		while ((stra = regexobject.url.exec(str)) !== null) {
			str = this.kinksFilter(stra, str, strict);
		}
		while ((stra = regexobject.reflinks.exec(str)) !== null) {
			helper1 = new RegExp('\\[' + stra[2] + '\\]: ?([^ \n]+)', "gi");
			if ((helper = helper1.exec(str)) !== null) {
				str = this.refLinksFilter(str, stra, helper, strict);
				trashgc.push(helper[0]);
			}
		}
		for (i = 0; i < trashgc.length; i++) {
			str = str.replace(trashgc[i], '');
		}
		while ((stra = regexobject.smlinks.exec(str)) !== null) {
			str = this.specialLinks(stra, str, strict);
		}

		/* horizontal line */
		while ((stra = regexobject.hr.exec(str)) !== null) {
			str = this.horizontalLineFilter(str, stra);
		}

		str = str.replace(/ {2,}[\n]{1,}/gmi, '

');
		return str;
	},
	htmlEncode: function (str) {
		var div = document.createElement('div');
		div.appendChild(document.createTextNode(str));
		str = div.innerHTML;
		div = undefined;
		return str;
	},
	mmdCSSclass: function (str, strict) {
		var urlTemp;
		if ((str.indexOf('/') !== -1) && (strict !== true)) {
			urlTemp = str.split('/');
			if (urlTemp[1].length === 0) {
				urlTemp = urlTemp[2].split('.');
			} else {
				urlTemp = urlTemp[0].split('.');
			}
			return 'class="mmd_' + urlTemp[urlTemp.length - 2].replace(/[^\w\d]/g, '') + urlTemp[urlTemp.length - 1] + '" ';
		}
		return '';
	}
};

EchoesWorks.md = micromarkdown;<commit_msg>extract table filter<commit_after>/*
 * micro-markdown.js
 * markdown in under 5kb
 *
 * Copyright 2014, Simon Waldherr - http://simon.waldherr.eu/
 * Released under the MIT Licence
 * http://simon.waldherr.eu/license/mit/
 *
 * Github:  https://github.com/simonwaldherr/micromarkdown.js/
 * Version: 0.3.0
 */

var micromarkdown = {
	regexobject: {
		headline: /^(\#{1,6})([^\#\n]+)$/m,
		code: /\s\`\`\`\n?([^`]+)\`\`\`/g,
		hr: /^(?:([\*\-_] ?)+)\1\1$/gm,
		lists: /^((\s*((\*|\-)|\d(\.|\))) [^\n]+)\n)+/gm,
		bolditalic: /(?:([\*_~]{1,3}))([^\*_~\n]+[^\*_~\s])\1/g,
		links: /!?\[([^\]<>]+)\]\(([^ \)<>]+)( "[^\(\)\"]+")?\)/g,
		reflinks: /\[([^\]]+)\]\[([^\]]+)\]/g,
		smlinks: /\@([a-z0-9]{3,})\@(t|gh|fb|gp|adn)/gi,
		mail: /<(([a-z0-9_\-\.])+\@([a-z0-9_\-\.])+\.([a-z]{2,7}))>/gmi,
		tables: /\n(([^|\n]+ *\| *)+([^|\n]+\n))((:?\-+:?\|)+(:?\-+:?)*\n)((([^|\n]+ *\| *)+([^|\n]+)\n)+)/g,
		include: /[\[<]include (\S+) from (https?:\/\/[a-z0-9\.\-]+\.[a-z]{2,9}[a-z0-9\.\-\?\&\/]+)[\]>]/gi,
		url: /<([a-zA-Z0-9@:%_\+.~#?&\/\/=]{2,256}\.[a-z]{2,4}\b(\/[\-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)?)>/g
	},

	codeFilter: function (str, stra) {
		return str.replace(stra[0], '\n' + micromarkdown.htmlEncode(stra[1]).replace(/\n/gm, '
').replace(/\ /gm, ' ') + '\n');
	},

	headlinesFilter: function (stra, str) {
		var count = stra[1].length;
		return str.replace(stra[0], '<h' + count + '>' + stra[2] + '</h' + count + '>' + '\n');
	},

	linksFilter: function (stra, str, strict) {
		if (stra[0].substr(0, 1) === '!') {
			str = str.replace(stra[0], '\n');
		} else {
			str = str.replace(stra[0], '' + stra[1] + '\n');
		}
		return str;
	},
	mailFilter: function (str, stra) {
		return str.replace(stra[0], '' + stra[1] + '');
	},
	horizontalLineFilter: function (str, stra) {
		str = str.replace(stra[0], '\n
\n');
		return str;
	},
	kinksFilter: function (stra, str, strict) {
		var repString = stra[1];
		if (repString.indexOf('://') === -1) {
			repString = 'http://' + repString;
		}
		str = str.replace(stra[0], '' + repString.replace(/(https:\/\/|http:\/\/|mailto:|ftp:\/\/)/gmi, '') + '');
		return str;
	},
	refLinksFilter: function (str, stra, helper, strict) {
		return str.replace(stra[0], '' + stra[1] + '');
	},
	specialLinks: function (stra, str, strict) {
		var repstr = "";
		switch (stra[2]) {
			case 't':
				repstr = 'https://twitter.com/' + stra[1];
				break;
			case 'gh':
				repstr = 'https://github.com/' + stra[1];
				break;
			case 'fb':
				repstr = 'https://www.facebook.com/' + stra[1];
				break;
			case 'gp':
				repstr = 'https://plus.google.com/+' + stra[1];
				break;
		}
		str = str.replace(stra[0], '' + stra[1] + '');
		return str;
	},
	boldItalicFilter: function (stra, str) {
		var repstr = [];
		if (stra[1] === '~~') {
			str = str.replace(stra[0], '' + stra[2] + '');
		} else {
			switch (stra[1].length) {
				case 1:
					repstr = ['', ''];
					break;
				case 2:
					repstr = ['', ''];
					break;
				case 3:
					repstr = ['', ''];
					break;
			}
			str = str.replace(stra[0], repstr[0] + stra[2] + repstr[1]);
		}
		return str;
	},
	tablesFilter: function (stra, strict, str) {
		var repstr, cel, helper, calign, helper1, helper2,
			i=0,
			j=0;

		repstr = '

';
		helper = stra[1].split('|');
		calign = stra[4].split('|');
		for (i = 0; i < helper.length; i++) {
			if (calign.length <= i) {
				calign.push(0);
			} else if ((calign[i].trimRight().slice(-1) === ':') && (strict !== true)) {
				if (calign[i][0] === ':') {
					calign[i] = 3;
				} else {
					calign[i] = 2;
				}
			} else if (strict !== true) {
				if (calign[i][0] === ':') {
					calign[i] = 1;
				} else {
					calign[i] = 0;
				}
			} else {
				calign[i] = 0;
			}
		}
		cel = ['', '', '', ''];
		for (i = 0; i < helper.length; i++) {
			repstr += cel[calign[i]] + helper[i].trim() + '	';
		}
		repstr += '
';
		cel = ['', '', '', ''];
		helper1 = stra[7].split('\n');
		for (i = 0; i < helper1.length; i++) {
			helper2 = helper1[i].split('|');
			if (helper2[0].length !== 0) {
				while (calign.length < helper2.length) {
					calign.push(0);
				}
				repstr += '
';
				for (j = 0; j < helper2.length; j++) {
					repstr += cel[calign[j]] + helper2[j].trim() + '	';
				}
				repstr += '
' + '\n';
			}
		}
		repstr += '
';
		str = str.replace(stra[0], repstr);
		return str;
	},

	parse: function (str, strict) {
		var line, nstatus = 0,
			status, indent, helper, helper1, repstr, stra, trashgc = [],
			casca = 0,
			i = 0;

		str = '\n' + str + '\n';

		var regexobject = micromarkdown.regexobject;
		if (strict !== true) {
			regexobject.lists = /^((\s*(\*|\d\.) [^\n]+)\n)+/gm;
		}

		/* code */
		while ((stra = regexobject.code.exec(str)) !== null) {
			str = this.codeFilter(str, stra);
		}

		/* headlines */
		while ((stra = regexobject.headline.exec(str)) !== null) {
			str = this.headlinesFilter(stra, str);
		}

		/* lists */
		while ((stra = regexobject.lists.exec(str)) !== null) {
			casca = 0;
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr = '
';
			} else {
				repstr = '
';
			}
			helper = stra[0].split('\n');
			helper1 = [];
			status = 0;
			indent = false;
			for (i = 0; i < helper.length; i++) {
				if ((line = /^((\s*)((\*|\-)|\d(\.|\))) ([^\n]+))/.exec(helper[i])) !== null) {
					if ((line[2] === undefined) || (line[2].length === 0)) {
						nstatus = 0;
					} else {
						if (indent === false) {
							indent = line[2].replace(/\t/, '    ').length;
						}
						nstatus = Math.round(line[2].replace(/\t/, '    ').length / indent);
					}
					while (status > nstatus) {
						repstr += helper1.pop();
						status--;
						casca--;
					}
					while (status < nstatus) {
						if ((line[0].trim().substr(0, 1) === '*') || (line[0].trim().substr(0, 1) === '-')) {
							repstr += '
';
							helper1.push('
');
						} else {
							repstr += '
';
							helper1.push('
');
						}
						status++;
						casca++;
					}
					repstr += '
' + line[6] + '
' + '\n';
				}
			}
			while (casca > 0) {
				repstr += '
';
				casca--;
			}
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr += '
';
			} else {
				repstr += '
';
			}
			str = str.replace(stra[0], repstr + '\n');
		}

		/* tables */
		while ((stra = regexobject.tables.exec(str)) !== null) {
			str = this.tablesFilter(stra, strict, str);
		}

		/* bold and italic */
		for (i = 0; i < 3; i++) {
			while ((stra = regexobject.bolditalic.exec(str)) !== null) {
				str = this.boldItalicFilter(stra, str);
			}
		}

		/* links */
		while ((stra = regexobject.links.exec(str)) !== null) {
			str = this.linksFilter(stra, str, strict);
		}
		while ((stra = regexobject.mail.exec(str)) !== null) {
			str = this.mailFilter(str, stra);
		}
		while ((stra = regexobject.url.exec(str)) !== null) {
			str = this.kinksFilter(stra, str, strict);
		}
		while ((stra = regexobject.reflinks.exec(str)) !== null) {
			helper1 = new RegExp('\\[' + stra[2] + '\\]: ?([^ \n]+)', "gi");
			if ((helper = helper1.exec(str)) !== null) {
				str = this.refLinksFilter(str, stra, helper, strict);
				trashgc.push(helper[0]);
			}
		}
		for (i = 0; i < trashgc.length; i++) {
			str = str.replace(trashgc[i], '');
		}
		while ((stra = regexobject.smlinks.exec(str)) !== null) {
			str = this.specialLinks(stra, str, strict);
		}

		/* horizontal line */
		while ((stra = regexobject.hr.exec(str)) !== null) {
			str = this.horizontalLineFilter(str, stra);
		}

		str = str.replace(/ {2,}[\n]{1,}/gmi, '

');
		return str;
	},
	htmlEncode: function (str) {
		var div = document.createElement('div');
		div.appendChild(document.createTextNode(str));
		str = div.innerHTML;
		div = undefined;
		return str;
	},
	mmdCSSclass: function (str, strict) {
		var urlTemp;
		if ((str.indexOf('/') !== -1) && (strict !== true)) {
			urlTemp = str.split('/');
			if (urlTemp[1].length === 0) {
				urlTemp = urlTemp[2].split('.');
			} else {
				urlTemp = urlTemp[0].split('.');
			}
			return 'class="mmd_' + urlTemp[urlTemp.length - 2].replace(/[^\w\d]/g, '') + urlTemp[urlTemp.length - 1] + '" ';
		}
		return '';
	}
};

EchoesWorks.md = micromarkdown;<|endoftext|>


<commit_before>// stream-test.js
//
// Test the stream module
//
// Copyright 2012, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var assert = require('assert'),
    vows = require('vows'),
    databank = require('databank'),
    Step = require('step'),
    URLMaker = require('../lib/urlmaker').URLMaker,
    modelBatch = require('./lib/model').modelBatch,
    schema = require('../lib/schema').schema,
    Databank = databank.Databank,
    DatabankObject = databank.DatabankObject;

var suite = vows.describe('stream interface');

// XXX: check other types

var testSchema = {
    pkey: "name"
};

var testData = {
    create: {
        name: "evan-inbox"
    },
    update: {
        something: "value" // Not clear what we update here
    }
};

// XXX: hack hack hack
// modelBatch hard-codes ActivityObject-style

var mb = modelBatch('stream', 'Stream', testSchema, testData);

// This class has a weird schema format

mb['When we require the stream module']
['and we get its Stream class export']
['and we get its schema']
['topic'] = function(Stream) {
    return Stream.schema.stream || null;
};

mb['When we require the stream module']
['and we get its Stream class export']
['and we create a stream instance']
['auto-generated fields are there'] = function(err, created) {
    // No auto-gen fields, so...
    assert.isTrue(true);
};

mb['When we require the stream module']
['and we get its Stream class export']
['and we create a stream instance']
['and we modify it']
['it is modified'] = function(err, updated) {
    assert.ifError(err);
};

suite.addBatch(mb);

var act1 = null;

suite.addBatch({
    'When we create a new stream': {
        topic: function() {
            var cb = this.callback;
            // Need this to make IDs

            URLMaker.hostname = "example.net";

            // Dummy databank

            var params = {schema: schema};

            var db = Databank.get('memory', params);

            db.connect({}, function(err) {

                var Stream, mod;

                if (err) {
                    cb(err, null);
                    return;
                }

                DatabankObject.bank = db;

                mod = require('../lib/model/stream');

                if (!mod) {
                    cb(new Error("No module"), null);
                    return;
                }

                Stream = mod.Stream;

                if (!Stream) {
                    cb(new Error("No class"), null);
                    return;
                }

                Stream.create({name: 'test'}, cb);
            });
        },
        'it works': function(err, stream) {
            assert.ifError(err);
            assert.isObject(stream);
        },
        'it has a deliver() method': function(err, stream) {
            assert.isFunction(stream.deliver);
        },
        'it has a getIDs() method': function(err, stream) {
            assert.isFunction(stream.getIDs);
        },
        'it has a count() method': function(err, stream) {
            assert.isFunction(stream.count);
        },
        'and we create a single activity': {
            topic: function(stream) {
                var Activity = require('../lib/model/activity').Activity,
                    props = {
                        actor: {
                            id: "urn:uuid:8f64087d-fffc-4fe0-9848-c18ae611cafd",
                            displayName: "Delbert Fnorgledap",
                            objectType: "person"
                        },
                        verb: "post",
                        object: {
                            objectType: "note",
                            content: "Feeling groovy."
                        }
                    };

                Activity.create(props, this.callback);
            },
            'it works': function(err, activity) {
                assert.ifError(err);
                assert.isObject(activity);
            },
            'and we deliver it to the stream': {
                topic: function(activity, stream) {
                    act1 = activity;
                    stream.deliver(activity.id, this.callback);
                },
                'it works': function(err) {
                    assert.ifError(err);
                },
                "and we get the stream's activities": {
                    topic: function(activity, stream) {
                        stream.getIDs(0, 100, this.callback);
                    },
                    'it works': function(err, activities) {
                        assert.ifError(err);
                        assert.isArray(activities);
                        assert.isTrue(activities.length > 0);
                    },
                    'our activity is in there': function(err, activities) {
                        assert.isTrue(activities.some(function(item) {
                            return item === act1.id;
                        }));
                    }
                },
                "and we count the stream's activities": {
                    topic: function(activity, stream) {
                        stream.count(this.callback);
                    },
                    'it works': function(err, cnt) {
                        assert.ifError(err);
                    },
                    'it has the right value (1)': function(err, cnt) {
                        assert.equal(cnt, 1);
                    }
                }
            }
        }
    }
});

suite.addBatch({

    'When we setup the env': {
        topic: function() {
            var cb = this.callback;

            // Need this to make IDs

            URLMaker.hostname = "example.net";

            // Dummy databank

            var params = {schema: schema};

            var db = Databank.get('memory', params);

            var stream = null;

            db.connect({}, function(err) {

                if (err) {
                    cb(err, null);
                    return;
                }

                DatabankObject.bank = db;

                var Stream = require('../lib/model/stream').Stream;
                cb(null, Stream);
            });
        },
        'it works': function(err, Stream) {
            assert.ifError(err);
        },
        'and we create a stream': {
            topic: function(Stream) {
                Stream.create('test-remove', this.callback);
            },
            'it works': function(err, stream) {
                assert.ifError(err);
                assert.isObject(stream);
            },
            'and we add 5000 ids': {
                topic: function(stream, Stream) {
                    var cb = this.callback();
                    Step(
                        function() {
                            var i, group = this.group();
                            for (i = 0; i < 5000; i++) {
                                stream.deliver('http://example.net/api/object/'+i, group());
                            }
                        },
                        function(err) {
                            if (err) {
                                cb(err);
                            } else {
                                cb(null);
                            }
                        }
                    );
                },
                'it works': function(err) {
                    assert.ifError(err);
                },
                'and we remove one': {
                    topic: function(stream, Stream) {
                        stream.remove('http://example.net/api/object/2500', this.callback());
                    },
                    'it works': function(err) {
                        assert.ifError(err);
                    },
                    'and we get all the IDs': {
                        topic: function(stream, Stream) {
                            stream.getIDs(0, 5000, this.callback);
                        },
                        'it works': function(err, ids) {
                            assert.ifError(err);
                            assert.isArray(ids);
                        },
                        'it is the right size': function(err, ids) {
                            assert.equal(ids.length, 4999); // 5000 - 1
                        },
                        'removed ID is missing': function(err, ids) {
                            assert.equal(ids.indexOf('http://example.net/api/object/2500'), -1);
                        }
                    }
                }
            }
        }
    }
});

suite.addBatch({
    'When we deliver a lot of activities to a stream': {
        topic: function() {
            var cb = this.callback,
                Activity = require('../lib/model/activity').Activity,
                actor = {
                    id: "urn:uuid:c484d84e-6afa-4c51-ac9a-f8738d48569c",
                    displayName: "Counter",
                    objectType: "service"
                };

            // Need this to make IDs

            URLMaker.hostname = "example.net";

            // Dummy databank

            var params = {schema: schema};

            var db = Databank.get('memory', params);

            var stream = null;

            Step(
                function() {
                    db.connect({}, this);
                },
                function(err) {
                    if (err) throw err;

                    DatabankObject.bank = db;

                    var Stream = require('../lib/model/stream').Stream;

                    Stream.create({name: 'scale-test'}, this);
                },
                function(err, results) {
                    var i, act, group = this.group();
                    if (err) throw err;
                    stream = results;
                    var addNew = function(act, callback) {
                        Activity.create(act, function(err, results) {
                            if (err) {
                                callback(err, null);
                            } else {
                                stream.deliver(results.id, function(err) {
                                    if (err) {
                                        callback(err, null);
                                    } else {
                                        callback(err, results);
                                    }
                                });
                            }
                        });
                    };
                    for (i = 0; i < 10000; i++) {
                        act = {actor: actor,
                               verb: "post",
                               object: {
                                   objectType: "note",
                                   content: "Note #" + i
                               }
                              };
                        addNew(act, group());
                    }
                },
                function(err, activities) {
                    if (err) {
                        cb(err, null);
                    } else {
                        cb(err, stream);
                    }
                }
            );
        },
        'it works': function(err, stream) {
            assert.ifError(err);
            assert.isObject(stream);
        },
        "and we count the number of elements": {
            topic: function(stream) {
                stream.count(this.callback);
            },
            "it works": function(err, cnt) {
                assert.ifError(err);
            },
            "it gives the right value (10000)": function(err, cnt) {
                assert.equal(cnt, 10000);
            }
        },
        "and we get all the activities in little chunks": {
            topic: function(stream) {
                var cb = this.callback;
                Step(
                    function() {
                        var i, group = this.group();
                        for (i = 0; i < 500; i++) {
                            stream.getIDs(i * 20, (i+1)*20, group());
                        }
                    },
                    function(err, chunks) {
                        if (err) {
                            cb(err, null);
                        } else {
                            cb(null, chunks);
                        }
                    }
                );
            },
            "it works": function(err, chunks) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunks) {
                var i;
                assert.lengthOf(chunks, 500);
                for (i = 0; i < 500; i++) {
                    assert.lengthOf(chunks[i], 20);
                }
            },
            "there are no duplicates": function(err, chunks) {
                var i, j, seen = {};
                for (i = 0; i < chunks.length; i++) {
                    for (j = 0; j < chunks[i].length; j++) {
                        assert.isUndefined(seen[chunks[i][j]]);
                        seen[chunks[i][j]] = true;
                    }
                }
            }
        },
        "and we get all the activities in big chunks": {
            topic: function(stream) {
                var cb = this.callback;
                Step(
                    function() {
                        var i, group = this.group();
                        for (i = 0; i < 20; i++) {
                            stream.getIDs(i * 500, (i+1)*500, group());
                        }
                    },
                    function(err, chunks) {
                        if (err) {
                            cb(err, null);
                        } else {
                            cb(null, chunks);
                        }
                    }
                );
            },
            "it works": function(err, chunks) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunks) {
                var i;
                assert.lengthOf(chunks, 20);
                for (i = 0; i < 20; i++) {
                    assert.lengthOf(chunks[i], 500);
                }
            },
            "there are no duplicates": function(err, chunks) {
                var i, j, seen = {};
                for (i = 0; i < chunks.length; i++) {
                    for (j = 0; j < chunks[i].length; j++) {
                        assert.isUndefined(seen[chunks[i][j]]);
                        seen[chunks[i][j]] = true;
                    }
                }
            }
        },
        "and we get all the activities one at a time": {
            topic: function(stream) {
                var cb = this.callback;
                Step(
                    function() {
                        var i, group = this.group();
                        for (i = 0; i < 10000; i++) {
                            stream.getIDs(i, i+1, group());
                        }
                    },
                    function(err, chunks) {
                        if (err) {
                            cb(err, null);
                        } else {
                            cb(null, chunks);
                        }
                    }
                );
            },
            "it works": function(err, chunks) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunks) {
                var i;
                assert.lengthOf(chunks, 10000);
                for (i = 0; i < 10000; i++) {
                    assert.lengthOf(chunks[i], 1);
                }
            },
            "there are no duplicates": function(err, chunks) {
                var i, j, seen = {};
                for (i = 0; i < chunks.length; i++) {
                    for (j = 0; j < chunks[i].length; j++) {
                        assert.isUndefined(seen[chunks[i][j]]);
                        seen[chunks[i][j]] = true;
                    }
                }
            }
        },
        "and we get all the activities at once": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(0, 10000, cb);
            },
            "it works": function(err, chunk) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunk) {
                assert.lengthOf(chunk, 10000);
            },
            "there are no duplicates": function(err, chunk) {
                var i, seen = {};
                for (i = 0; i < chunk.length; i++) {
                    assert.isUndefined(seen[chunk[i]]);
                    seen[chunk[i]] = true;
                }
            }
        },
        "and we try to get activities starting at a negative number": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(-10, 20, function(err, activities) {
                    if (err) {
                        cb(null);
                    } else {
                        cb(new Error("Unexpected success!"));
                    }
                });
            },
            'it fails correctly': function(err) {
                assert.ifError(err);
            }
        },
        "and we try to get activities ending at a negative number": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(10, -20, function(err, activities) {
                    if (err) {
                        cb(null);
                    } else {
                        cb(new Error("Unexpected success!"));
                    }
                });
            },
            'it fails correctly': function(err) {
                assert.ifError(err);
            }
        },
        "and we try to get activities with start after the end": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(110, 100, function(err, activities) {
                    if (err) {
                        cb(null);
                    } else {
                        cb(new Error("Unexpected success!"));
                    }
                });
            },
            'it fails correctly': function(err) {
                assert.ifError(err);
            }
        },
        "and we try to get activities start and end equal": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(50, 50, cb);
            },
            'it works': function(err, results) {
                assert.ifError(err);
            },
            'results are empty': function(err, results) {
                assert.isEmpty(results);
            }
        }
    }
});

suite['export'](module);<commit_msg>Wrong arguments to Stream.create()<commit_after>// stream-test.js
//
// Test the stream module
//
// Copyright 2012, StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var assert = require('assert'),
    vows = require('vows'),
    databank = require('databank'),
    Step = require('step'),
    URLMaker = require('../lib/urlmaker').URLMaker,
    modelBatch = require('./lib/model').modelBatch,
    schema = require('../lib/schema').schema,
    Databank = databank.Databank,
    DatabankObject = databank.DatabankObject;

var suite = vows.describe('stream interface');

// XXX: check other types

var testSchema = {
    pkey: "name"
};

var testData = {
    create: {
        name: "evan-inbox"
    },
    update: {
        something: "value" // Not clear what we update here
    }
};

// XXX: hack hack hack
// modelBatch hard-codes ActivityObject-style

var mb = modelBatch('stream', 'Stream', testSchema, testData);

// This class has a weird schema format

mb['When we require the stream module']
['and we get its Stream class export']
['and we get its schema']
['topic'] = function(Stream) {
    return Stream.schema.stream || null;
};

mb['When we require the stream module']
['and we get its Stream class export']
['and we create a stream instance']
['auto-generated fields are there'] = function(err, created) {
    // No auto-gen fields, so...
    assert.isTrue(true);
};

mb['When we require the stream module']
['and we get its Stream class export']
['and we create a stream instance']
['and we modify it']
['it is modified'] = function(err, updated) {
    assert.ifError(err);
};

suite.addBatch(mb);

var act1 = null;

suite.addBatch({
    'When we create a new stream': {
        topic: function() {
            var cb = this.callback;
            // Need this to make IDs

            URLMaker.hostname = "example.net";

            // Dummy databank

            var params = {schema: schema};

            var db = Databank.get('memory', params);

            db.connect({}, function(err) {

                var Stream, mod;

                if (err) {
                    cb(err, null);
                    return;
                }

                DatabankObject.bank = db;

                mod = require('../lib/model/stream');

                if (!mod) {
                    cb(new Error("No module"), null);
                    return;
                }

                Stream = mod.Stream;

                if (!Stream) {
                    cb(new Error("No class"), null);
                    return;
                }

                Stream.create({name: 'test'}, cb);
            });
        },
        'it works': function(err, stream) {
            assert.ifError(err);
            assert.isObject(stream);
        },
        'it has a deliver() method': function(err, stream) {
            assert.isFunction(stream.deliver);
        },
        'it has a getIDs() method': function(err, stream) {
            assert.isFunction(stream.getIDs);
        },
        'it has a count() method': function(err, stream) {
            assert.isFunction(stream.count);
        },
        'and we create a single activity': {
            topic: function(stream) {
                var Activity = require('../lib/model/activity').Activity,
                    props = {
                        actor: {
                            id: "urn:uuid:8f64087d-fffc-4fe0-9848-c18ae611cafd",
                            displayName: "Delbert Fnorgledap",
                            objectType: "person"
                        },
                        verb: "post",
                        object: {
                            objectType: "note",
                            content: "Feeling groovy."
                        }
                    };

                Activity.create(props, this.callback);
            },
            'it works': function(err, activity) {
                assert.ifError(err);
                assert.isObject(activity);
            },
            'and we deliver it to the stream': {
                topic: function(activity, stream) {
                    act1 = activity;
                    stream.deliver(activity.id, this.callback);
                },
                'it works': function(err) {
                    assert.ifError(err);
                },
                "and we get the stream's activities": {
                    topic: function(activity, stream) {
                        stream.getIDs(0, 100, this.callback);
                    },
                    'it works': function(err, activities) {
                        assert.ifError(err);
                        assert.isArray(activities);
                        assert.isTrue(activities.length > 0);
                    },
                    'our activity is in there': function(err, activities) {
                        assert.isTrue(activities.some(function(item) {
                            return item === act1.id;
                        }));
                    }
                },
                "and we count the stream's activities": {
                    topic: function(activity, stream) {
                        stream.count(this.callback);
                    },
                    'it works': function(err, cnt) {
                        assert.ifError(err);
                    },
                    'it has the right value (1)': function(err, cnt) {
                        assert.equal(cnt, 1);
                    }
                }
            }
        }
    }
});

suite.addBatch({

    'When we setup the env': {
        topic: function() {
            var cb = this.callback;

            // Need this to make IDs

            URLMaker.hostname = "example.net";

            // Dummy databank

            var params = {schema: schema};

            var db = Databank.get('memory', params);

            var stream = null;

            db.connect({}, function(err) {

                if (err) {
                    cb(err, null);
                    return;
                }

                DatabankObject.bank = db;

                var Stream = require('../lib/model/stream').Stream;
                cb(null, Stream);
            });
        },
        'it works': function(err, Stream) {
            assert.ifError(err);
        },
        'and we create a stream': {
            topic: function(Stream) {
                Stream.create({name: 'test-remove'}, this.callback);
            },
            'it works': function(err, stream) {
                assert.ifError(err);
                assert.isObject(stream);
            },
            'and we add 5000 ids': {
                topic: function(stream, Stream) {
                    var cb = this.callback();
                    Step(
                        function() {
                            var i, group = this.group();
                            for (i = 0; i < 5000; i++) {
                                stream.deliver('http://example.net/api/object/'+i, group());
                            }
                        },
                        function(err) {
                            if (err) {
                                cb(err);
                            } else {
                                cb(null);
                            }
                        }
                    );
                },
                'it works': function(err) {
                    assert.ifError(err);
                },
                'and we remove one': {
                    topic: function(stream, Stream) {
                        stream.remove('http://example.net/api/object/2500', this.callback());
                    },
                    'it works': function(err) {
                        assert.ifError(err);
                    },
                    'and we get all the IDs': {
                        topic: function(stream, Stream) {
                            stream.getIDs(0, 5000, this.callback);
                        },
                        'it works': function(err, ids) {
                            assert.ifError(err);
                            assert.isArray(ids);
                        },
                        'it is the right size': function(err, ids) {
                            assert.equal(ids.length, 4999); // 5000 - 1
                        },
                        'removed ID is missing': function(err, ids) {
                            assert.equal(ids.indexOf('http://example.net/api/object/2500'), -1);
                        }
                    }
                }
            }
        }
    }
});

suite.addBatch({
    'When we deliver a lot of activities to a stream': {
        topic: function() {
            var cb = this.callback,
                Activity = require('../lib/model/activity').Activity,
                actor = {
                    id: "urn:uuid:c484d84e-6afa-4c51-ac9a-f8738d48569c",
                    displayName: "Counter",
                    objectType: "service"
                };

            // Need this to make IDs

            URLMaker.hostname = "example.net";

            // Dummy databank

            var params = {schema: schema};

            var db = Databank.get('memory', params);

            var stream = null;

            Step(
                function() {
                    db.connect({}, this);
                },
                function(err) {
                    if (err) throw err;

                    DatabankObject.bank = db;

                    var Stream = require('../lib/model/stream').Stream;

                    Stream.create({name: 'scale-test'}, this);
                },
                function(err, results) {
                    var i, act, group = this.group();
                    if (err) throw err;
                    stream = results;
                    var addNew = function(act, callback) {
                        Activity.create(act, function(err, results) {
                            if (err) {
                                callback(err, null);
                            } else {
                                stream.deliver(results.id, function(err) {
                                    if (err) {
                                        callback(err, null);
                                    } else {
                                        callback(err, results);
                                    }
                                });
                            }
                        });
                    };
                    for (i = 0; i < 10000; i++) {
                        act = {actor: actor,
                               verb: "post",
                               object: {
                                   objectType: "note",
                                   content: "Note #" + i
                               }
                              };
                        addNew(act, group());
                    }
                },
                function(err, activities) {
                    if (err) {
                        cb(err, null);
                    } else {
                        cb(err, stream);
                    }
                }
            );
        },
        'it works': function(err, stream) {
            assert.ifError(err);
            assert.isObject(stream);
        },
        "and we count the number of elements": {
            topic: function(stream) {
                stream.count(this.callback);
            },
            "it works": function(err, cnt) {
                assert.ifError(err);
            },
            "it gives the right value (10000)": function(err, cnt) {
                assert.equal(cnt, 10000);
            }
        },
        "and we get all the activities in little chunks": {
            topic: function(stream) {
                var cb = this.callback;
                Step(
                    function() {
                        var i, group = this.group();
                        for (i = 0; i < 500; i++) {
                            stream.getIDs(i * 20, (i+1)*20, group());
                        }
                    },
                    function(err, chunks) {
                        if (err) {
                            cb(err, null);
                        } else {
                            cb(null, chunks);
                        }
                    }
                );
            },
            "it works": function(err, chunks) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunks) {
                var i;
                assert.lengthOf(chunks, 500);
                for (i = 0; i < 500; i++) {
                    assert.lengthOf(chunks[i], 20);
                }
            },
            "there are no duplicates": function(err, chunks) {
                var i, j, seen = {};
                for (i = 0; i < chunks.length; i++) {
                    for (j = 0; j < chunks[i].length; j++) {
                        assert.isUndefined(seen[chunks[i][j]]);
                        seen[chunks[i][j]] = true;
                    }
                }
            }
        },
        "and we get all the activities in big chunks": {
            topic: function(stream) {
                var cb = this.callback;
                Step(
                    function() {
                        var i, group = this.group();
                        for (i = 0; i < 20; i++) {
                            stream.getIDs(i * 500, (i+1)*500, group());
                        }
                    },
                    function(err, chunks) {
                        if (err) {
                            cb(err, null);
                        } else {
                            cb(null, chunks);
                        }
                    }
                );
            },
            "it works": function(err, chunks) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunks) {
                var i;
                assert.lengthOf(chunks, 20);
                for (i = 0; i < 20; i++) {
                    assert.lengthOf(chunks[i], 500);
                }
            },
            "there are no duplicates": function(err, chunks) {
                var i, j, seen = {};
                for (i = 0; i < chunks.length; i++) {
                    for (j = 0; j < chunks[i].length; j++) {
                        assert.isUndefined(seen[chunks[i][j]]);
                        seen[chunks[i][j]] = true;
                    }
                }
            }
        },
        "and we get all the activities one at a time": {
            topic: function(stream) {
                var cb = this.callback;
                Step(
                    function() {
                        var i, group = this.group();
                        for (i = 0; i < 10000; i++) {
                            stream.getIDs(i, i+1, group());
                        }
                    },
                    function(err, chunks) {
                        if (err) {
                            cb(err, null);
                        } else {
                            cb(null, chunks);
                        }
                    }
                );
            },
            "it works": function(err, chunks) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunks) {
                var i;
                assert.lengthOf(chunks, 10000);
                for (i = 0; i < 10000; i++) {
                    assert.lengthOf(chunks[i], 1);
                }
            },
            "there are no duplicates": function(err, chunks) {
                var i, j, seen = {};
                for (i = 0; i < chunks.length; i++) {
                    for (j = 0; j < chunks[i].length; j++) {
                        assert.isUndefined(seen[chunks[i][j]]);
                        seen[chunks[i][j]] = true;
                    }
                }
            }
        },
        "and we get all the activities at once": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(0, 10000, cb);
            },
            "it works": function(err, chunk) {
                assert.ifError(err);
            },
            "results have right size": function(err, chunk) {
                assert.lengthOf(chunk, 10000);
            },
            "there are no duplicates": function(err, chunk) {
                var i, seen = {};
                for (i = 0; i < chunk.length; i++) {
                    assert.isUndefined(seen[chunk[i]]);
                    seen[chunk[i]] = true;
                }
            }
        },
        "and we try to get activities starting at a negative number": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(-10, 20, function(err, activities) {
                    if (err) {
                        cb(null);
                    } else {
                        cb(new Error("Unexpected success!"));
                    }
                });
            },
            'it fails correctly': function(err) {
                assert.ifError(err);
            }
        },
        "and we try to get activities ending at a negative number": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(10, -20, function(err, activities) {
                    if (err) {
                        cb(null);
                    } else {
                        cb(new Error("Unexpected success!"));
                    }
                });
            },
            'it fails correctly': function(err) {
                assert.ifError(err);
            }
        },
        "and we try to get activities with start after the end": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(110, 100, function(err, activities) {
                    if (err) {
                        cb(null);
                    } else {
                        cb(new Error("Unexpected success!"));
                    }
                });
            },
            'it fails correctly': function(err) {
                assert.ifError(err);
            }
        },
        "and we try to get activities start and end equal": {
            topic: function(stream) {
                var cb = this.callback;
                stream.getIDs(50, 50, cb);
            },
            'it works': function(err, results) {
                assert.ifError(err);
            },
            'results are empty': function(err, results) {
                assert.isEmpty(results);
            }
        }
    }
});

suite['export'](module);<|endoftext|>


<commit_before>/**
 * All promises are wrapped in a try/catch. Normally, you
 * want this. It is very important for operations that run
 * the risk of failure, such as writing to localStorage, to
 * reject the Promise. Rejection is better than raising an
 * exception in these circumstances.
 *
 * However there comes a time when you are *done* with a
 * Promise, yet this try/catch leaks out into other branches
 * of code. This is bad: JavaScript's permissive try/catch
 * which also traps runtime errors like bad syntax.
 *
 * We want to avoid this for operations internal to Microcosm,
 * like dispatching an action when it finishes resolving. The
 * outside world does not care about these things, only that
 * pushing an action succeeded or failed.
 *
 * Additionally, we absolutely must know about exceptions that
 * are raised within Microcosm, store handlers, and plugin handlers.
 *
 * In this function we attempt to execute a callback and, if it
 * fails, we eventually throw the error using setTimeout. This
 * circumvents a Promise's try/catch because setTimeout pushes
 * its callback into a completely new callstack on the next cycle
 * of the JavaScript runtime loop.
 *
 * This has no effect on callbacks outside of Microcosm, since the
 * chain of callbacks is never returned.
 *
 * @param {Promise} promise - The promise to escape out of.
 * @param {Function} callback - The error-first callback to execute.
 *
 * @returns undefined
 */

import eventually from './eventually'

export default function liftCallback (promise, callback) {

  function success (body) {
    callback(null, body)
  }

  function failure (error) {
    callback(error || new Error('Rejected Promise'), null)
  }

  promise.then(success, failure).catch(eventually.throws)
}
<commit_msg>Completely rename liftCallback to unpromise<commit_after>/**
 * All promises are wrapped in a try/catch. Normally, you
 * want this. It is very important for operations that run
 * the risk of failure, such as writing to localStorage, to
 * reject the Promise. Rejection is better than raising an
 * exception in these circumstances.
 *
 * However there comes a time when you are *done* with a
 * Promise, yet this try/catch leaks out into other branches
 * of code. This is bad: JavaScript's permissive try/catch
 * which also traps runtime errors like bad syntax.
 *
 * We want to avoid this for operations internal to Microcosm,
 * like dispatching an action when it finishes resolving. The
 * outside world does not care about these things, only that
 * pushing an action succeeded or failed.
 *
 * Additionally, we absolutely must know about exceptions that
 * are raised within Microcosm, store handlers, and plugin handlers.
 *
 * In this function we attempt to execute a callback and, if it
 * fails, we eventually throw the error using setTimeout. This
 * circumvents a Promise's try/catch because setTimeout pushes
 * its callback into a completely new callstack on the next cycle
 * of the JavaScript runtime loop.
 *
 * This has no effect on callbacks outside of Microcosm, since the
 * chain of callbacks is never returned.
 *
 * @param {Promise} promise - The promise to escape out of.
 * @param {Function} callback - The error-first callback to execute.
 *
 * @returns undefined
 */

import eventually from './eventually'

export default function unpromise (promise, callback) {

  function success (body) {
    callback(null, body)
  }

  function failure (error) {
    callback(error || new Error('Rejected Promise'), null)
  }

  promise.then(success, failure).catch(eventually.throws)
}
<|endoftext|>


<commit_before>import logger from 'winston'
import pem from 'pem'
import Q from 'q'
import { DbVersionModel } from './model/dbVersion'
import { KeystoreModel } from './model/keystore'
import { ClientModel } from './model/clients'
import { UserModel } from './model/users'
import { VisualizerModel } from './model/visualizer'

function dedupName (name, names, num) {
  let newName
  if (num) {
    newName = `${name} ${num}`
  } else {
    newName = name
  }
  if (Array.from(names).includes(newName)) {
    if (!num) {
      num = 1
    }
    return dedupName(name, names, ++num)
  } else {
    return newName
  }
}

// push new upgrade functions to this array, function must return a promise
// Warning: only add new function below existing functions, order matters!
const upgradeFuncs = []

upgradeFuncs.push({
  description: 'Ensure that all certs have a fingerprint property',
  func () {
    const defer = Q.defer()

    KeystoreModel.findOne((err, keystore) => {
      if (err) { return callback(err) }
      if (!keystore) { return defer.resolve() }

      // convert server cert
      return pem.getFingerprint(keystore.cert.data, (err, obj) => {
        if (err) { return callback(err) }
        keystore.cert.fingerprint = obj.fingerprint

        const promises = []
        for (let i = 0; i < keystore.ca.length; i++) {
          const cert = keystore.ca[i]
          const caDefer = Q.defer()
          promises.push(caDefer.promise)
          pem.getFingerprint(cert.data, (err, obj) => {
            if (err) { return callback(err) }
            keystore.ca[i].fingerprint = obj.fingerprint
            return caDefer.resolve()
          })
        }

        Q.all(promises).then(() =>
          keystore.save((err) => {
            if (err != null) { logger.error(`Failed to save keystore: ${err}`) }
            return defer.resolve()
          })
        )
      })
    })

    return defer.promise
  }
})

upgradeFuncs.push({
  description: 'Convert clients link to certs via their domain to use the cert fingerprint instead',
  func () {
    const defer = Q.defer()

    ClientModel.find((err, clients) => {
      if (err != null) {
        logger.error(`Couldn't fetch all clients to upgrade db: ${err}`)
        return defer.reject()
      }

      KeystoreModel.findOne((err, keystore) => {
        if (err != null) {
          logger.error(`Couldn't fetch keystore to upgrade db: ${err}`)
          return defer.reject()
        }

        const promises = []
        for (const client of Array.from(clients)) {
          const clientDefer = Q.defer()
          promises.push(clientDefer.promise)

          if ((keystore != null ? keystore.ca : undefined) != null) {
            for (const cert of Array.from(keystore.ca)) {
              if ((client.clientDomain === cert.commonName) && (client.certFingerprint == null)) {
                client.certFingerprint = cert.fingerprint
                break
              }
            }
          }

          (clientDefer =>
              client.save((err) => {
                if (err != null) {
                  logger.error(`Couldn't save client ${client.clientID} while upgrading db: ${err}`)
                  return clientDefer.reject()
                }

                return clientDefer.resolve()
              }))(clientDefer)
        }

        Q.all(promises).then(() => defer.resolve())
      })
    })

    return defer.promise
  }
})

// Adapt visualizer from an old version (core 2.0.0, console 1.6.0 and earlier)
//
// We follow the same migration strategy as console:
// https://github.com/jembi/openhim-console/blob/1047b49db2050bafa6b4797e3788fa716d1760b3/app/scripts/controllers/profile.js#L83-L109
function adaptOldVisualizerStructure (visualizer) {
  visualizer.channels = []
  visualizer.mediators = []
  visualizer.time.minDisplayPeriod = 100

  if (visualizer.endpoints) {
    for (const endpoint of Array.from(visualizer.endpoints)) {
      visualizer.channels.push({
        eventType: 'channel',
        eventName: endpoint.event.replace('channel-', ''),
        display: endpoint.desc
      })
    }
    delete visualizer.endpoints
  }

  if (visualizer.components) {
    return (() => {
      const result = []
      for (const component of Array.from(visualizer.components)) {
        const split = component.event.split('-')
        if (split.length > 1) {
          component.eventType = split[0]
          component.eventName = split[1]
        } else {
          component.eventType = 'channel'
          component.eventName = component.event
        }
        component.display = component.desc
        delete component.event
        result.push(delete component.desc)
      }
      return result
    })()
  }
}

upgradeFuncs.push({
  description: 'Migrate visualizer setting from a user\'s profile to a shared collection',
  func () {
    const defer = Q.defer()
    UserModel.find((err, users) => {
      if (err) {
        return Q.defer().reject(err)
      }

      const visNames = []
      const promises = []
      users.forEach((user) => {
        if ((user.settings != null ? user.settings.visualizer : undefined) != null) {
          let vis = user.settings.visualizer
          if (((vis.components != null ? vis.components.length : undefined) > 0) || ((vis.mediators != null ? vis.mediators.length : undefined) > 0) || ((vis.channels != null ? vis.channels.length : undefined) > 0) || ((vis.endpoints != null ? vis.endpoints.length : undefined) > 0)) {
            const userDefer = Q.defer()
            promises.push(userDefer.promise)

            if (vis.endpoints) { // old version
              adaptOldVisualizerStructure(vis)
            }

            let name = `${user.firstname} ${user.surname}'s visualizer`
            name = dedupName(name, visNames)
            vis.name = name
            visNames.push(name)

            vis = new VisualizerModel(vis)
            logger.debug(`Migrating visualizer from user profile ${user.email}, using visualizer name '${name}'`)
            vis.save((err, vis) => {
              if (err) {
                logger.error(`Error migrating visualizer from user profile ${user.email}: ${err.stack}`)
                return userDefer.reject(err)
              }

              // delete the visualizer settings from this user profile
              user.set('settings.visualizer', null)
              user.save((err, user) => {
                if (err) { return userDefer.reject(err) }
                return userDefer.resolve()
              })
            })
          }
        }
      })

      Q.all(promises).then(() => defer.resolve()).catch(err => defer.reject(err))
    })

    return defer.promise
  }
})

if (process.env.NODE_ENV === 'test') {
  exports.upgradeFuncs = upgradeFuncs
  exports.dedupName = dedupName
}

async function upgradeDbInternal () {
  try {
    const dbVer = (await DbVersionModel.findOne()) || new DbVersionModel({version: 0, lastUpdated: new Date()})
    const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version)

    for (const upgradeFunc of upgradeFuncsToRun) {
      await upgradeFunc.func()
      dbVer.version++
      dbVer.lastUpdated = new Date()
      await dbVer.save()
    }

    if (upgradeFuncsToRun.length === 0) {
      logger.info('No database upgrades needed')
    } else {
      logger.info('Completed database upgrade')
    }
  } catch (err) {
    logger.error(`There was an error upgrading your database, you will need to fix this manually to continue. ${err.stack}`)
  }
}

export function upgradeDb (callback) {
  upgradeDbInternal()
    .then((...values) => {
      if (callback) {
        callback(null, ...(values || []))
      }
    })
    .catch(err => {
      if (callback) {
        callback(err)
      }
    })
}

if (!module.parent) {
  exports.upgradeDb(() => process.exit())
}
<commit_msg>Re-add return<commit_after>import logger from 'winston'
import pem from 'pem'
import Q from 'q'
import { DbVersionModel } from './model/dbVersion'
import { KeystoreModel } from './model/keystore'
import { ClientModel } from './model/clients'
import { UserModel } from './model/users'
import { VisualizerModel } from './model/visualizer'

function dedupName (name, names, num) {
  let newName
  if (num) {
    newName = `${name} ${num}`
  } else {
    newName = name
  }
  if (Array.from(names).includes(newName)) {
    if (!num) {
      num = 1
    }
    return dedupName(name, names, ++num)
  } else {
    return newName
  }
}

// push new upgrade functions to this array, function must return a promise
// Warning: only add new function below existing functions, order matters!
const upgradeFuncs = []

upgradeFuncs.push({
  description: 'Ensure that all certs have a fingerprint property',
  func () {
    const defer = Q.defer()

    KeystoreModel.findOne((err, keystore) => {
      if (err) { return callback(err) }
      if (!keystore) { return defer.resolve() }

      // convert server cert
      return pem.getFingerprint(keystore.cert.data, (err, obj) => {
        if (err) { return callback(err) }
        keystore.cert.fingerprint = obj.fingerprint

        const promises = []
        for (let i = 0; i < keystore.ca.length; i++) {
          const cert = keystore.ca[i]
          const caDefer = Q.defer()
          promises.push(caDefer.promise)
          pem.getFingerprint(cert.data, (err, obj) => {
            if (err) { return callback(err) }
            keystore.ca[i].fingerprint = obj.fingerprint
            return caDefer.resolve()
          })
        }

        Q.all(promises).then(() =>
          keystore.save((err) => {
            if (err != null) { logger.error(`Failed to save keystore: ${err}`) }
            return defer.resolve()
          })
        )
      })
    })

    return defer.promise
  }
})

upgradeFuncs.push({
  description: 'Convert clients link to certs via their domain to use the cert fingerprint instead',
  func () {
    const defer = Q.defer()

    ClientModel.find((err, clients) => {
      if (err != null) {
        logger.error(`Couldn't fetch all clients to upgrade db: ${err}`)
        return defer.reject()
      }

      KeystoreModel.findOne((err, keystore) => {
        if (err != null) {
          logger.error(`Couldn't fetch keystore to upgrade db: ${err}`)
          return defer.reject()
        }

        const promises = []
        for (const client of Array.from(clients)) {
          const clientDefer = Q.defer()
          promises.push(clientDefer.promise)

          if ((keystore != null ? keystore.ca : undefined) != null) {
            for (const cert of Array.from(keystore.ca)) {
              if ((client.clientDomain === cert.commonName) && (client.certFingerprint == null)) {
                client.certFingerprint = cert.fingerprint
                break
              }
            }
          }

          (clientDefer =>
              client.save((err) => {
                if (err != null) {
                  logger.error(`Couldn't save client ${client.clientID} while upgrading db: ${err}`)
                  return clientDefer.reject()
                }

                return clientDefer.resolve()
              }))(clientDefer)
        }

        Q.all(promises).then(() => defer.resolve())
      })
    })

    return defer.promise
  }
})

// Adapt visualizer from an old version (core 2.0.0, console 1.6.0 and earlier)
//
// We follow the same migration strategy as console:
// https://github.com/jembi/openhim-console/blob/1047b49db2050bafa6b4797e3788fa716d1760b3/app/scripts/controllers/profile.js#L83-L109
function adaptOldVisualizerStructure (visualizer) {
  visualizer.channels = []
  visualizer.mediators = []
  visualizer.time.minDisplayPeriod = 100

  if (visualizer.endpoints) {
    for (const endpoint of Array.from(visualizer.endpoints)) {
      visualizer.channels.push({
        eventType: 'channel',
        eventName: endpoint.event.replace('channel-', ''),
        display: endpoint.desc
      })
    }
    delete visualizer.endpoints
  }

  if (visualizer.components) {
    return (() => {
      const result = []
      for (const component of Array.from(visualizer.components)) {
        const split = component.event.split('-')
        if (split.length > 1) {
          component.eventType = split[0]
          component.eventName = split[1]
        } else {
          component.eventType = 'channel'
          component.eventName = component.event
        }
        component.display = component.desc
        delete component.event
        result.push(delete component.desc)
      }
      return result
    })()
  }
}

upgradeFuncs.push({
  description: 'Migrate visualizer setting from a user\'s profile to a shared collection',
  func () {
    const defer = Q.defer()
    UserModel.find((err, users) => {
      if (err) {
        return Q.defer().reject(err)
      }

      const visNames = []
      const promises = []
      users.forEach((user) => {
        if ((user.settings != null ? user.settings.visualizer : undefined) != null) {
          let vis = user.settings.visualizer
          if (((vis.components != null ? vis.components.length : undefined) > 0) || ((vis.mediators != null ? vis.mediators.length : undefined) > 0) || ((vis.channels != null ? vis.channels.length : undefined) > 0) || ((vis.endpoints != null ? vis.endpoints.length : undefined) > 0)) {
            const userDefer = Q.defer()
            promises.push(userDefer.promise)

            if (vis.endpoints) { // old version
              adaptOldVisualizerStructure(vis)
            }

            let name = `${user.firstname} ${user.surname}'s visualizer`
            name = dedupName(name, visNames)
            vis.name = name
            visNames.push(name)

            vis = new VisualizerModel(vis)
            logger.debug(`Migrating visualizer from user profile ${user.email}, using visualizer name '${name}'`)
            vis.save((err, vis) => {
              if (err) {
                logger.error(`Error migrating visualizer from user profile ${user.email}: ${err.stack}`)
                return userDefer.reject(err)
              }

              // delete the visualizer settings from this user profile
              user.set('settings.visualizer', null)
              user.save((err, user) => {
                if (err) { return userDefer.reject(err) }
                return userDefer.resolve()
              })
            })
          }
        }
      })

      Q.all(promises).then(() => defer.resolve()).catch(err => defer.reject(err))
    })

    return defer.promise
  }
})

if (process.env.NODE_ENV === 'test') {
  exports.upgradeFuncs = upgradeFuncs
  exports.dedupName = dedupName
}

async function upgradeDbInternal () {
  try {
    const dbVer = (await DbVersionModel.findOne()) || new DbVersionModel({version: 0, lastUpdated: new Date()})
    const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version)

    for (const upgradeFunc of upgradeFuncsToRun) {
      await upgradeFunc.func()
      dbVer.version++
      dbVer.lastUpdated = new Date()
      await dbVer.save()
    }

    if (upgradeFuncsToRun.length === 0) {
      logger.info('No database upgrades needed')
    } else {
      logger.info('Completed database upgrade')
    }
  } catch (err) {
    logger.error(`There was an error upgrading your database, you will need to fix this manually to continue. ${err.stack}`)
  }
}

export function upgradeDb (callback) {
  return upgradeDbInternal()
    .then((...values) => {
      if (callback) {
        callback(null, ...(values || []))
      }
    })
    .catch(err => {
      if (callback) {
        callback(err)
      }
    })
}

if (!module.parent) {
  exports.upgradeDb(() => process.exit())
}
<|endoftext|>


<commit_before>const snekfetch = require('snekfetch');
const Constants = require('./Constants');

/**
 * Contains various general-purpose utility methods. These functions are also available on the base `Discord` object.
 */
class Util {
  constructor() {
    throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
  }

  /**
   * Splits a string into multiple chunks at a designated character that do not exceed a specific length.
   * @param {string} text Content to split
   * @param {SplitOptions} [options] Options controlling the behaviour of the split
   * @returns {string|string[]}
   */
  static splitMessage(text, { maxLength = 1950, char = '\n', prepend = '', append = '' } = {}) {
    if (text.length <= maxLength) return text;
    const splitText = text.split(char);
    if (splitText.length === 1) throw new Error('Message exceeds the max length and contains no split characters.');
    const messages = [''];
    let msg = 0;
    for (let i = 0; i < splitText.length; i++) {
      if (messages[msg].length + splitText[i].length + 1 > maxLength) {
        messages[msg] += append;
        messages.push(prepend);
        msg++;
      }
      messages[msg] += (messages[msg].length > 0 && messages[msg] !== prepend ? char : '') + splitText[i];
    }
    return messages;
  }

  /**
   * Escapes any Discord-flavour markdown in a string.
   * @param {string} text Content to escape
   * @param {boolean} [onlyCodeBlock=false] Whether to only escape codeblocks (takes priority)
   * @param {boolean} [onlyInlineCode=false] Whether to only escape inline code
   * @returns {string}
   */
  static escapeMarkdown(text, onlyCodeBlock = false, onlyInlineCode = false) {
    if (onlyCodeBlock) return text.replace(/```/g, '`\u200b``');
    if (onlyInlineCode) return text.replace(/\\(`|\\)/g, '$1').replace(/(`|\\)/g, '\\$1');
    return text.replace(/\\(\*|_|`|~|\\)/g, '$1').replace(/(\*|_|`|~|\\)/g, '\\$1');
  }

  /**
   * Gets the recommended shard count from Discord.
   * @param {string} token Discord auth token
   * @param {number} [guildsPerShard=1000] Number of guilds per shard
   * @returns {Promise<number>} the recommended number of shards
   */
  static fetchRecommendedShards(token, guildsPerShard = 1000) {
    return new Promise((resolve, reject) => {
      if (!token) throw new Error('A token must be provided.');
      snekfetch.get(`${Constants.DefaultOptions.http.host}/api/v${Constants.DefaultOptions.http.version}${Constants.Endpoints.gateway.bot}`)
        .set('Authorization', `Bot ${token.replace(/^Bot\s*/i, '')}`)
        .end((err, res) => {
          if (err) reject(err);
          resolve(res.body.shards * (1000 / guildsPerShard));
        });
    });
  }

  /**
   * Parses emoji info out of a string. The string must be one of:
   * - A UTF-8 emoji (no ID)
   * - A URL-encoded UTF-8 emoji (no ID)
   * - A Discord custom emoji (`<:name:id>`)
   * @param {string} text Emoji string to parse
   * @returns {Object} Object with `name` and `id` properties
   * @private
   */
  static parseEmoji(text) {
    if (text.includes('%')) text = decodeURIComponent(text);
    if (text.includes(':')) {
      const [name, id] = text.split(':');
      return { name, id };
    } else {
      return {
        name: text,
        id: null,
      };
    }
  }

  /**
   * Does some weird shit to test the equality of two arrays' elements.
   * <warn>Do not use. This will give your dog/cat severe untreatable cancer of the everything. RIP Fluffykins.</warn>
   * @param {Array<*>} a ????
   * @param {Array<*>} b ?????????
   * @returns {boolean}
   * @private
   */
  static arraysEqual(a, b) {
    if (a === b) return true;
    if (a.length !== b.length) return false;

    for (const itemInd in a) {
      const item = a[itemInd];
      const ind = b.indexOf(item);
      if (ind) b.splice(ind, 1);
    }

    return b.length === 0;
  }

  /**
   * Shallow-copies an object with its class/prototype intact.
   * @param {Object} obj Object to clone
   * @returns {Object}
   * @private
   */
  static cloneObject(obj) {
    return Object.assign(Object.create(obj), obj);
  }

  /**
   * Sets default properties on an object that aren't already specified.
   * @param {Object} def Default properties
   * @param {Object} given Object to assign defaults to
   * @returns {Object}
   * @private
   */
  static mergeDefault(def, given) {
    if (!given) return def;
    for (const key in def) {
      if (!{}.hasOwnProperty.call(given, key)) {
        given[key] = def[key];
      } else if (given[key] === Object(given[key])) {
        given[key] = this.mergeDefault(def[key], given[key]);
      }
    }

    return given;
  }

  /**
   * Converts an ArrayBuffer or string to a Buffer.
   * @param {ArrayBuffer|string} ab ArrayBuffer to convert
   * @returns {Buffer}
   * @private
   */
  static convertToBuffer(ab) {
    if (typeof ab === 'string') ab = this.str2ab(ab);
    return Buffer.from(ab);
  }

  /**
   * Converts a string to an ArrayBuffer.
   * @param {string} str String to convert
   * @returns {ArrayBuffer}
   * @private
   */
  static str2ab(str) {
    const buffer = new ArrayBuffer(str.length * 2);
    const view = new Uint16Array(buffer);
    for (var i = 0, strLen = str.length; i < strLen; i++) view[i] = str.charCodeAt(i);
    return buffer;
  }

  /**
   * Makes an Error from a plain info object
   * @param {Object} obj Error info
   * @param {string} obj.name Error type
   * @param {string} obj.message Message for the error
   * @param {string} obj.stack Stack for the error
   * @returns {Error}
   * @private
   */
  static makeError(obj) {
    const err = new Error(obj.message);
    err.name = obj.name;
    err.stack = obj.stack;
    return err;
  }

  /**
   * Makes a plain error info object from an Error
   * @param {Error} err Error to get info from
   * @returns {Object}
   * @private
   */
  static makePlainError(err) {
    const obj = {};
    obj.name = err.name;
    obj.message = err.message;
    obj.stack = err.stack;
    return obj;
  }

  /**
   * Moves an element in an array *in place*
   * @param {Array<*>} array Array to modify
   * @param {*} element Element to move
   * @param {number} newIndex Index or offset to move the element to
   * @param {boolean} [offset=false] Move the element by an offset amount rather than to a set index
   * @returns {Array<*>}
   * @private
   */
  static moveElementInArray(array, element, newIndex, offset = false) {
    const index = array.indexOf(element);
    newIndex = (offset ? index : 0) + newIndex;
    if (newIndex > -1 && newIndex < array.length) {
      const removedElement = array.splice(index, 1)[0];
      array.splice(newIndex, 0, removedElement);
    }
    return array;
  }
}

module.exports = Util;
<commit_msg>Fix travis build error<commit_after>const snekfetch = require('snekfetch');
const Constants = require('./Constants');
const ConstantsHttp = Constants.DefaultOptions.http;

/**
 * Contains various general-purpose utility methods. These functions are also available on the base `Discord` object.
 */
class Util {
  constructor() {
    throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
  }

  /**
   * Splits a string into multiple chunks at a designated character that do not exceed a specific length.
   * @param {string} text Content to split
   * @param {SplitOptions} [options] Options controlling the behaviour of the split
   * @returns {string|string[]}
   */
  static splitMessage(text, { maxLength = 1950, char = '\n', prepend = '', append = '' } = {}) {
    if (text.length <= maxLength) return text;
    const splitText = text.split(char);
    if (splitText.length === 1) throw new Error('Message exceeds the max length and contains no split characters.');
    const messages = [''];
    let msg = 0;
    for (let i = 0; i < splitText.length; i++) {
      if (messages[msg].length + splitText[i].length + 1 > maxLength) {
        messages[msg] += append;
        messages.push(prepend);
        msg++;
      }
      messages[msg] += (messages[msg].length > 0 && messages[msg] !== prepend ? char : '') + splitText[i];
    }
    return messages;
  }

  /**
   * Escapes any Discord-flavour markdown in a string.
   * @param {string} text Content to escape
   * @param {boolean} [onlyCodeBlock=false] Whether to only escape codeblocks (takes priority)
   * @param {boolean} [onlyInlineCode=false] Whether to only escape inline code
   * @returns {string}
   */
  static escapeMarkdown(text, onlyCodeBlock = false, onlyInlineCode = false) {
    if (onlyCodeBlock) return text.replace(/```/g, '`\u200b``');
    if (onlyInlineCode) return text.replace(/\\(`|\\)/g, '$1').replace(/(`|\\)/g, '\\$1');
    return text.replace(/\\(\*|_|`|~|\\)/g, '$1').replace(/(\*|_|`|~|\\)/g, '\\$1');
  }

  /**
   * Gets the recommended shard count from Discord.
   * @param {string} token Discord auth token
   * @param {number} [guildsPerShard=1000] Number of guilds per shard
   * @returns {Promise<number>} the recommended number of shards
   */
  static fetchRecommendedShards(token, guildsPerShard = 1000) {
    return new Promise((resolve, reject) => {
      if (!token) throw new Error('A token must be provided.');
      snekfetch.get(`${ConstantsHttp.host}/api/v${ConstantsHttp.version}${Constants.Endpoints.gateway.bot}`)
        .set('Authorization', `Bot ${token.replace(/^Bot\s*/i, '')}`)
        .end((err, res) => {
          if (err) reject(err);
          resolve(res.body.shards * (1000 / guildsPerShard));
        });
    });
  }

  /**
   * Parses emoji info out of a string. The string must be one of:
   * - A UTF-8 emoji (no ID)
   * - A URL-encoded UTF-8 emoji (no ID)
   * - A Discord custom emoji (`<:name:id>`)
   * @param {string} text Emoji string to parse
   * @returns {Object} Object with `name` and `id` properties
   * @private
   */
  static parseEmoji(text) {
    if (text.includes('%')) text = decodeURIComponent(text);
    if (text.includes(':')) {
      const [name, id] = text.split(':');
      return { name, id };
    } else {
      return {
        name: text,
        id: null,
      };
    }
  }

  /**
   * Does some weird shit to test the equality of two arrays' elements.
   * <warn>Do not use. This will give your dog/cat severe untreatable cancer of the everything. RIP Fluffykins.</warn>
   * @param {Array<*>} a ????
   * @param {Array<*>} b ?????????
   * @returns {boolean}
   * @private
   */
  static arraysEqual(a, b) {
    if (a === b) return true;
    if (a.length !== b.length) return false;

    for (const itemInd in a) {
      const item = a[itemInd];
      const ind = b.indexOf(item);
      if (ind) b.splice(ind, 1);
    }

    return b.length === 0;
  }

  /**
   * Shallow-copies an object with its class/prototype intact.
   * @param {Object} obj Object to clone
   * @returns {Object}
   * @private
   */
  static cloneObject(obj) {
    return Object.assign(Object.create(obj), obj);
  }

  /**
   * Sets default properties on an object that aren't already specified.
   * @param {Object} def Default properties
   * @param {Object} given Object to assign defaults to
   * @returns {Object}
   * @private
   */
  static mergeDefault(def, given) {
    if (!given) return def;
    for (const key in def) {
      if (!{}.hasOwnProperty.call(given, key)) {
        given[key] = def[key];
      } else if (given[key] === Object(given[key])) {
        given[key] = this.mergeDefault(def[key], given[key]);
      }
    }

    return given;
  }

  /**
   * Converts an ArrayBuffer or string to a Buffer.
   * @param {ArrayBuffer|string} ab ArrayBuffer to convert
   * @returns {Buffer}
   * @private
   */
  static convertToBuffer(ab) {
    if (typeof ab === 'string') ab = this.str2ab(ab);
    return Buffer.from(ab);
  }

  /**
   * Converts a string to an ArrayBuffer.
   * @param {string} str String to convert
   * @returns {ArrayBuffer}
   * @private
   */
  static str2ab(str) {
    const buffer = new ArrayBuffer(str.length * 2);
    const view = new Uint16Array(buffer);
    for (var i = 0, strLen = str.length; i < strLen; i++) view[i] = str.charCodeAt(i);
    return buffer;
  }

  /**
   * Makes an Error from a plain info object
   * @param {Object} obj Error info
   * @param {string} obj.name Error type
   * @param {string} obj.message Message for the error
   * @param {string} obj.stack Stack for the error
   * @returns {Error}
   * @private
   */
  static makeError(obj) {
    const err = new Error(obj.message);
    err.name = obj.name;
    err.stack = obj.stack;
    return err;
  }

  /**
   * Makes a plain error info object from an Error
   * @param {Error} err Error to get info from
   * @returns {Object}
   * @private
   */
  static makePlainError(err) {
    const obj = {};
    obj.name = err.name;
    obj.message = err.message;
    obj.stack = err.stack;
    return obj;
  }

  /**
   * Moves an element in an array *in place*
   * @param {Array<*>} array Array to modify
   * @param {*} element Element to move
   * @param {number} newIndex Index or offset to move the element to
   * @param {boolean} [offset=false] Move the element by an offset amount rather than to a set index
   * @returns {Array<*>}
   * @private
   */
  static moveElementInArray(array, element, newIndex, offset = false) {
    const index = array.indexOf(element);
    newIndex = (offset ? index : 0) + newIndex;
    if (newIndex > -1 && newIndex < array.length) {
      const removedElement = array.splice(index, 1)[0];
      array.splice(newIndex, 0, removedElement);
    }
    return array;
  }
}

module.exports = Util;
<|endoftext|>


<commit_before>/**
 * @file zui-base
 * @author xiaowu
 */

window.zui = {};
<commit_msg>delete: 删除没用文件<commit_after><|endoftext|>